spatelier 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- analytics/__init__.py +1 -0
- analytics/reporter.py +497 -0
- cli/__init__.py +1 -0
- cli/app.py +147 -0
- cli/audio.py +129 -0
- cli/cli_analytics.py +320 -0
- cli/cli_utils.py +282 -0
- cli/error_handlers.py +122 -0
- cli/files.py +299 -0
- cli/update.py +325 -0
- cli/video.py +823 -0
- cli/worker.py +615 -0
- core/__init__.py +1 -0
- core/analytics_dashboard.py +368 -0
- core/base.py +303 -0
- core/base_service.py +69 -0
- core/config.py +345 -0
- core/database_service.py +116 -0
- core/decorators.py +263 -0
- core/error_handler.py +210 -0
- core/file_tracker.py +254 -0
- core/interactive_cli.py +366 -0
- core/interfaces.py +166 -0
- core/job_queue.py +437 -0
- core/logger.py +79 -0
- core/package_updater.py +469 -0
- core/progress.py +228 -0
- core/service_factory.py +295 -0
- core/streaming.py +299 -0
- core/worker.py +765 -0
- database/__init__.py +1 -0
- database/connection.py +265 -0
- database/metadata.py +516 -0
- database/models.py +288 -0
- database/repository.py +592 -0
- database/transcription_storage.py +219 -0
- modules/__init__.py +1 -0
- modules/audio/__init__.py +5 -0
- modules/audio/converter.py +197 -0
- modules/video/__init__.py +16 -0
- modules/video/converter.py +191 -0
- modules/video/fallback_extractor.py +334 -0
- modules/video/services/__init__.py +18 -0
- modules/video/services/audio_extraction_service.py +274 -0
- modules/video/services/download_service.py +852 -0
- modules/video/services/metadata_service.py +190 -0
- modules/video/services/playlist_service.py +445 -0
- modules/video/services/transcription_service.py +491 -0
- modules/video/transcription_service.py +385 -0
- modules/video/youtube_api.py +397 -0
- spatelier/__init__.py +33 -0
- spatelier-0.3.0.dist-info/METADATA +260 -0
- spatelier-0.3.0.dist-info/RECORD +59 -0
- spatelier-0.3.0.dist-info/WHEEL +5 -0
- spatelier-0.3.0.dist-info/entry_points.txt +2 -0
- spatelier-0.3.0.dist-info/licenses/LICENSE +21 -0
- spatelier-0.3.0.dist-info/top_level.txt +7 -0
- utils/__init__.py +1 -0
- utils/helpers.py +250 -0
database/models.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database models for Spatelier.
|
|
3
|
+
|
|
4
|
+
This module defines SQLAlchemy models for storing media files, processing history,
|
|
5
|
+
and analytics data.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from sqlalchemy import JSON, Boolean, Column, DateTime
|
|
13
|
+
from sqlalchemy import Enum as SQLEnum
|
|
14
|
+
from sqlalchemy import Float, ForeignKey, Integer, String, Text, create_engine
|
|
15
|
+
from sqlalchemy.orm import declarative_base, relationship, sessionmaker
|
|
16
|
+
from sqlalchemy.sql import func
|
|
17
|
+
|
|
18
|
+
Base = declarative_base()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MediaType(str, Enum):
|
|
22
|
+
"""Media type enumeration."""
|
|
23
|
+
|
|
24
|
+
VIDEO = "video"
|
|
25
|
+
AUDIO = "audio"
|
|
26
|
+
IMAGE = "image"
|
|
27
|
+
DOCUMENT = "document"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ProcessingStatus(str, Enum):
|
|
31
|
+
"""Processing status enumeration."""
|
|
32
|
+
|
|
33
|
+
PENDING = "pending"
|
|
34
|
+
PROCESSING = "processing"
|
|
35
|
+
COMPLETED = "completed"
|
|
36
|
+
FAILED = "failed"
|
|
37
|
+
CANCELLED = "cancelled"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class MediaFile(Base):
|
|
41
|
+
"""Media file model."""
|
|
42
|
+
|
|
43
|
+
__tablename__ = "media_files"
|
|
44
|
+
|
|
45
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
46
|
+
file_path = Column(
|
|
47
|
+
String(1000), nullable=False, index=True
|
|
48
|
+
) # Remove unique constraint
|
|
49
|
+
file_name = Column(String(500), nullable=False)
|
|
50
|
+
file_size = Column(Integer, nullable=False)
|
|
51
|
+
file_hash = Column(String(64), nullable=False, index=True)
|
|
52
|
+
|
|
53
|
+
# OS-level file identification for tracking moved files
|
|
54
|
+
file_device = Column(Integer, nullable=True, index=True) # st_dev
|
|
55
|
+
file_inode = Column(Integer, nullable=True, index=True) # st_ino
|
|
56
|
+
file_identifier = Column(
|
|
57
|
+
String(50), nullable=True, unique=True, index=True
|
|
58
|
+
) # device:inode
|
|
59
|
+
|
|
60
|
+
media_type = Column(SQLEnum(MediaType), nullable=False, index=True)
|
|
61
|
+
mime_type = Column(String(100), nullable=False)
|
|
62
|
+
duration = Column(Float, nullable=True) # For video/audio files
|
|
63
|
+
width = Column(Integer, nullable=True) # For video files
|
|
64
|
+
height = Column(Integer, nullable=True) # For video files
|
|
65
|
+
bitrate = Column(Integer, nullable=True) # For audio/video files
|
|
66
|
+
sample_rate = Column(Integer, nullable=True) # For audio files
|
|
67
|
+
channels = Column(Integer, nullable=True) # For audio files
|
|
68
|
+
codec = Column(String(50), nullable=True)
|
|
69
|
+
|
|
70
|
+
# Video-specific metadata
|
|
71
|
+
title = Column(String(1000), nullable=True)
|
|
72
|
+
description = Column(Text, nullable=True)
|
|
73
|
+
uploader = Column(String(200), nullable=True)
|
|
74
|
+
uploader_id = Column(String(100), nullable=True)
|
|
75
|
+
upload_date = Column(DateTime, nullable=True)
|
|
76
|
+
view_count = Column(Integer, nullable=True)
|
|
77
|
+
like_count = Column(Integer, nullable=True)
|
|
78
|
+
dislike_count = Column(Integer, nullable=True)
|
|
79
|
+
comment_count = Column(Integer, nullable=True)
|
|
80
|
+
tags = Column(Text, nullable=True) # JSON array of tags
|
|
81
|
+
categories = Column(Text, nullable=True) # JSON array of categories
|
|
82
|
+
language = Column(String(10), nullable=True)
|
|
83
|
+
age_limit = Column(Integer, nullable=True)
|
|
84
|
+
|
|
85
|
+
# Source information
|
|
86
|
+
source_url = Column(String(1000), nullable=True, index=True)
|
|
87
|
+
source_platform = Column(
|
|
88
|
+
String(50), nullable=True, index=True
|
|
89
|
+
) # youtube, vimeo, etc.
|
|
90
|
+
source_id = Column(String(100), nullable=True, index=True) # video ID on platform
|
|
91
|
+
source_title = Column(String(1000), nullable=True)
|
|
92
|
+
source_description = Column(Text, nullable=True)
|
|
93
|
+
|
|
94
|
+
# Technical metadata
|
|
95
|
+
fps = Column(Float, nullable=True) # Frames per second
|
|
96
|
+
aspect_ratio = Column(String(20), nullable=True) # e.g., "16:9"
|
|
97
|
+
color_space = Column(String(50), nullable=True)
|
|
98
|
+
audio_codec = Column(String(50), nullable=True)
|
|
99
|
+
video_codec = Column(String(50), nullable=True)
|
|
100
|
+
|
|
101
|
+
# Thumbnail and artwork
|
|
102
|
+
thumbnail_url = Column(String(1000), nullable=True)
|
|
103
|
+
thumbnail_path = Column(String(1000), nullable=True)
|
|
104
|
+
|
|
105
|
+
created_at = Column(DateTime, default=func.now(), nullable=False)
|
|
106
|
+
updated_at = Column(
|
|
107
|
+
DateTime, default=func.now(), onupdate=func.now(), nullable=False
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Relationships
|
|
111
|
+
processing_jobs = relationship("ProcessingJob", back_populates="media_file")
|
|
112
|
+
analytics_events = relationship("AnalyticsEvent", back_populates="media_file")
|
|
113
|
+
playlist_videos = relationship("PlaylistVideo", back_populates="media_file")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ProcessingJob(Base):
|
|
117
|
+
"""Processing job model."""
|
|
118
|
+
|
|
119
|
+
__tablename__ = "processing_jobs"
|
|
120
|
+
|
|
121
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
122
|
+
media_file_id = Column(Integer, ForeignKey("media_files.id"), nullable=True)
|
|
123
|
+
job_type = Column(
|
|
124
|
+
String(100), nullable=False, index=True
|
|
125
|
+
) # download, convert, extract, etc.
|
|
126
|
+
status = Column(SQLEnum(ProcessingStatus), nullable=False, index=True)
|
|
127
|
+
input_path = Column(String(1000), nullable=False)
|
|
128
|
+
output_path = Column(String(1000), nullable=True)
|
|
129
|
+
parameters = Column(Text, nullable=True) # JSON string of processing parameters
|
|
130
|
+
error_message = Column(Text, nullable=True)
|
|
131
|
+
started_at = Column(DateTime, nullable=True)
|
|
132
|
+
completed_at = Column(DateTime, nullable=True)
|
|
133
|
+
duration_seconds = Column(Float, nullable=True)
|
|
134
|
+
created_at = Column(DateTime, default=func.now(), nullable=False)
|
|
135
|
+
|
|
136
|
+
# Relationships
|
|
137
|
+
media_file = relationship("MediaFile", back_populates="processing_jobs")
|
|
138
|
+
analytics_events = relationship("AnalyticsEvent", back_populates="processing_job")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class AnalyticsEvent(Base):
|
|
142
|
+
"""Analytics event model."""
|
|
143
|
+
|
|
144
|
+
__tablename__ = "analytics_events"
|
|
145
|
+
|
|
146
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
147
|
+
media_file_id = Column(Integer, ForeignKey("media_files.id"), nullable=True)
|
|
148
|
+
processing_job_id = Column(Integer, ForeignKey("processing_jobs.id"), nullable=True)
|
|
149
|
+
event_type = Column(
|
|
150
|
+
String(100), nullable=False, index=True
|
|
151
|
+
) # download, convert, view, etc.
|
|
152
|
+
event_data = Column(Text, nullable=True) # JSON string of event data
|
|
153
|
+
timestamp = Column(DateTime, default=func.now(), nullable=False, index=True)
|
|
154
|
+
user_id = Column(String(100), nullable=True, index=True)
|
|
155
|
+
session_id = Column(String(100), nullable=True, index=True)
|
|
156
|
+
|
|
157
|
+
# Relationships
|
|
158
|
+
media_file = relationship("MediaFile", back_populates="analytics_events")
|
|
159
|
+
processing_job = relationship("ProcessingJob", back_populates="analytics_events")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class DownloadSource(Base):
|
|
163
|
+
"""Download source model."""
|
|
164
|
+
|
|
165
|
+
__tablename__ = "download_sources"
|
|
166
|
+
|
|
167
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
168
|
+
url = Column(String(1000), nullable=False, unique=True, index=True)
|
|
169
|
+
domain = Column(String(200), nullable=False, index=True)
|
|
170
|
+
title = Column(String(500), nullable=True)
|
|
171
|
+
description = Column(Text, nullable=True)
|
|
172
|
+
duration = Column(Float, nullable=True)
|
|
173
|
+
uploader = Column(String(200), nullable=True)
|
|
174
|
+
upload_date = Column(DateTime, nullable=True)
|
|
175
|
+
view_count = Column(Integer, nullable=True)
|
|
176
|
+
like_count = Column(Integer, nullable=True)
|
|
177
|
+
download_count = Column(Integer, default=0, nullable=False)
|
|
178
|
+
last_downloaded = Column(DateTime, nullable=True)
|
|
179
|
+
created_at = Column(DateTime, default=func.now(), nullable=False)
|
|
180
|
+
updated_at = Column(
|
|
181
|
+
DateTime, default=func.now(), onupdate=func.now(), nullable=False
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class UserPreference(Base):
|
|
186
|
+
"""User preference model."""
|
|
187
|
+
|
|
188
|
+
__tablename__ = "user_preferences"
|
|
189
|
+
|
|
190
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
191
|
+
user_id = Column(String(100), nullable=False, index=True)
|
|
192
|
+
preference_key = Column(String(200), nullable=False, index=True)
|
|
193
|
+
preference_value = Column(Text, nullable=False)
|
|
194
|
+
created_at = Column(DateTime, default=func.now(), nullable=False)
|
|
195
|
+
updated_at = Column(
|
|
196
|
+
DateTime, default=func.now(), onupdate=func.now(), nullable=False
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
__table_args__ = ({"extend_existing": True},)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class Playlist(Base):
|
|
203
|
+
"""Playlist model for organizing videos."""
|
|
204
|
+
|
|
205
|
+
__tablename__ = "playlists"
|
|
206
|
+
|
|
207
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
208
|
+
playlist_id = Column(
|
|
209
|
+
String(100), nullable=False, unique=True, index=True
|
|
210
|
+
) # YouTube playlist ID
|
|
211
|
+
title = Column(String(1000), nullable=False)
|
|
212
|
+
description = Column(Text, nullable=True)
|
|
213
|
+
uploader = Column(String(200), nullable=True)
|
|
214
|
+
uploader_id = Column(String(100), nullable=True)
|
|
215
|
+
source_url = Column(String(1000), nullable=True, index=True)
|
|
216
|
+
source_platform = Column(String(50), nullable=True, index=True)
|
|
217
|
+
video_count = Column(Integer, nullable=True)
|
|
218
|
+
view_count = Column(Integer, nullable=True)
|
|
219
|
+
thumbnail_url = Column(String(1000), nullable=True)
|
|
220
|
+
created_at = Column(DateTime, default=func.now(), nullable=False)
|
|
221
|
+
updated_at = Column(
|
|
222
|
+
DateTime, default=func.now(), onupdate=func.now(), nullable=False
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Relationships
|
|
226
|
+
playlist_videos = relationship(
|
|
227
|
+
"PlaylistVideo", back_populates="playlist", cascade="all, delete-orphan"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class PlaylistVideo(Base):
|
|
232
|
+
"""Junction table for playlist-video relationships."""
|
|
233
|
+
|
|
234
|
+
__tablename__ = "playlist_videos"
|
|
235
|
+
|
|
236
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
237
|
+
playlist_id = Column(
|
|
238
|
+
Integer, ForeignKey("playlists.id"), nullable=False, index=True
|
|
239
|
+
)
|
|
240
|
+
media_file_id = Column(
|
|
241
|
+
Integer, ForeignKey("media_files.id"), nullable=False, index=True
|
|
242
|
+
)
|
|
243
|
+
position = Column(Integer, nullable=True) # Order in playlist
|
|
244
|
+
video_title = Column(String(1000), nullable=True) # Title at time of addition
|
|
245
|
+
added_at = Column(DateTime, default=func.now(), nullable=False)
|
|
246
|
+
|
|
247
|
+
# Relationships
|
|
248
|
+
playlist = relationship("Playlist", back_populates="playlist_videos")
|
|
249
|
+
media_file = relationship("MediaFile", back_populates="playlist_videos")
|
|
250
|
+
|
|
251
|
+
# Unique constraint to prevent duplicate entries
|
|
252
|
+
__table_args__ = ({"extend_existing": True},)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class Transcription(Base):
|
|
256
|
+
"""SQLite transcription storage (JSON + FTS-backed search)."""
|
|
257
|
+
|
|
258
|
+
__tablename__ = "transcriptions"
|
|
259
|
+
|
|
260
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
261
|
+
media_file_id = Column(
|
|
262
|
+
Integer, ForeignKey("media_files.id"), nullable=False, index=True
|
|
263
|
+
)
|
|
264
|
+
language = Column(String(10), nullable=True)
|
|
265
|
+
duration = Column(Float, nullable=True)
|
|
266
|
+
processing_time = Column(Float, nullable=True)
|
|
267
|
+
model_used = Column(String(100), nullable=True)
|
|
268
|
+
segments_json = Column(JSON, nullable=False)
|
|
269
|
+
full_text = Column(Text, nullable=False)
|
|
270
|
+
created_at = Column(DateTime, default=func.now(), nullable=False)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class APIKeys(Base):
|
|
274
|
+
"""API keys and credentials storage."""
|
|
275
|
+
|
|
276
|
+
__tablename__ = "api_keys"
|
|
277
|
+
|
|
278
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
279
|
+
service_name = Column(String, nullable=False) # e.g., 'youtube', 'openai'
|
|
280
|
+
key_value = Column(Text, nullable=False) # Encrypted API key
|
|
281
|
+
is_active = Column(Boolean, default=True, nullable=False)
|
|
282
|
+
created_at = Column(DateTime, default=func.now(), nullable=False)
|
|
283
|
+
updated_at = Column(
|
|
284
|
+
DateTime, default=func.now(), onupdate=func.now(), nullable=False
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
def __repr__(self):
|
|
288
|
+
return f"<APIKeys(id={self.id}, service='{self.service_name}')>"
|