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.
Files changed (59) hide show
  1. analytics/__init__.py +1 -0
  2. analytics/reporter.py +497 -0
  3. cli/__init__.py +1 -0
  4. cli/app.py +147 -0
  5. cli/audio.py +129 -0
  6. cli/cli_analytics.py +320 -0
  7. cli/cli_utils.py +282 -0
  8. cli/error_handlers.py +122 -0
  9. cli/files.py +299 -0
  10. cli/update.py +325 -0
  11. cli/video.py +823 -0
  12. cli/worker.py +615 -0
  13. core/__init__.py +1 -0
  14. core/analytics_dashboard.py +368 -0
  15. core/base.py +303 -0
  16. core/base_service.py +69 -0
  17. core/config.py +345 -0
  18. core/database_service.py +116 -0
  19. core/decorators.py +263 -0
  20. core/error_handler.py +210 -0
  21. core/file_tracker.py +254 -0
  22. core/interactive_cli.py +366 -0
  23. core/interfaces.py +166 -0
  24. core/job_queue.py +437 -0
  25. core/logger.py +79 -0
  26. core/package_updater.py +469 -0
  27. core/progress.py +228 -0
  28. core/service_factory.py +295 -0
  29. core/streaming.py +299 -0
  30. core/worker.py +765 -0
  31. database/__init__.py +1 -0
  32. database/connection.py +265 -0
  33. database/metadata.py +516 -0
  34. database/models.py +288 -0
  35. database/repository.py +592 -0
  36. database/transcription_storage.py +219 -0
  37. modules/__init__.py +1 -0
  38. modules/audio/__init__.py +5 -0
  39. modules/audio/converter.py +197 -0
  40. modules/video/__init__.py +16 -0
  41. modules/video/converter.py +191 -0
  42. modules/video/fallback_extractor.py +334 -0
  43. modules/video/services/__init__.py +18 -0
  44. modules/video/services/audio_extraction_service.py +274 -0
  45. modules/video/services/download_service.py +852 -0
  46. modules/video/services/metadata_service.py +190 -0
  47. modules/video/services/playlist_service.py +445 -0
  48. modules/video/services/transcription_service.py +491 -0
  49. modules/video/transcription_service.py +385 -0
  50. modules/video/youtube_api.py +397 -0
  51. spatelier/__init__.py +33 -0
  52. spatelier-0.3.0.dist-info/METADATA +260 -0
  53. spatelier-0.3.0.dist-info/RECORD +59 -0
  54. spatelier-0.3.0.dist-info/WHEEL +5 -0
  55. spatelier-0.3.0.dist-info/entry_points.txt +2 -0
  56. spatelier-0.3.0.dist-info/licenses/LICENSE +21 -0
  57. spatelier-0.3.0.dist-info/top_level.txt +7 -0
  58. utils/__init__.py +1 -0
  59. 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}')>"