media-toolkit 0.2.2__tar.gz → 0.2.4__tar.gz

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 (32) hide show
  1. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/PKG-INFO +59 -11
  2. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/README.md +59 -11
  3. media_toolkit-0.2.4/media_toolkit/__init__.py +4 -0
  4. media_toolkit-0.2.4/media_toolkit/core/IMediaFile.py +86 -0
  5. media_toolkit-0.2.4/media_toolkit/core/MediaDict.py +353 -0
  6. media_toolkit-0.2.4/media_toolkit/core/MediaList.py +219 -0
  7. media_toolkit-0.2.4/media_toolkit/core/__init__.py +9 -0
  8. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit/core/audio_file.py +1 -1
  9. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit/core/image_file.py +2 -4
  10. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit/core/media_file.py +24 -12
  11. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit/core/video/video_file.py +5 -2
  12. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit/utils/__init__.py +2 -0
  13. media_toolkit-0.2.4/media_toolkit/utils/auto_async.py +10 -0
  14. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit/utils/dependency_requirements.py +5 -1
  15. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit/utils/file_conversion.py +19 -11
  16. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit.egg-info/PKG-INFO +59 -11
  17. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit.egg-info/SOURCES.txt +4 -0
  18. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/pyproject.toml +1 -1
  19. media_toolkit-0.2.2/media_toolkit/__init__.py +0 -2
  20. media_toolkit-0.2.2/media_toolkit/core/__init__.py +0 -4
  21. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/LICENSE +0 -0
  22. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit/core/file_content_buffer.py +0 -0
  23. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit/core/video/__init__.py +0 -0
  24. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit/core/video/video_utils.py +0 -0
  25. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit/utils/generator_wrapper.py +0 -0
  26. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit/utils/utils.py +0 -0
  27. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit.egg-info/dependency_links.txt +0 -0
  28. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit.egg-info/requires.txt +0 -0
  29. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/media_toolkit.egg-info/top_level.txt +0 -0
  30. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/setup.cfg +0 -0
  31. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/test/test_image_file.py +0 -0
  32. {media_toolkit-0.2.2 → media_toolkit-0.2.4}/test/test_video_file.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: media-toolkit
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Web-ready standardized file processing and serialization. Read, load and convert to standard file types with a common interface.
5
5
  Author: SocAIty
6
6
  License: MIT License
@@ -49,11 +49,11 @@ Requires-Dist: vidgear[core]; extra == "videofile"
49
49
  Dynamic: license-file
50
50
 
51
51
 
52
- <h1 align="center" style="margin-top:-25px">MediaToolkit</h1>
52
+ <h1 align="center" style="margin-top:-25px">MediaToolkit</h1>
53
53
  <p align="center">
54
54
  <img align="center" src="docs/media-file-icon.png" height="200" />
55
55
  </p>
56
- <h3 align="center" style="margin-top:-10px">Web-ready standardized file processing and serialization</h3>
56
+ <h3 align="center" style="margin-top:-10px">Web-ready standardized file processing and serialization</h3>
57
57
 
58
58
 
59
59
  # Features
@@ -64,7 +64,7 @@ Especially useful for code that works with multiple file types like images, audi
64
64
  Load and convert from and to common data types:
65
65
  - numpy arrays
66
66
  - file paths
67
- - bytes,
67
+ - bytes
68
68
  - base64
69
69
  - json
70
70
  - urls
@@ -92,6 +92,7 @@ pip install media-toolkit[VideoFile] # or [AudioFile, VideoFile, ...]
92
92
  # install from github for newest release
93
93
  pip install git+git://github.com/SocAIty/media-toolkit
94
94
  ```
95
+
95
96
  The package checks if you have missing dependencies for certain file types while using.
96
97
  Use the ```--no-deps``` flag for a minimal tiny pure python installation.
97
98
  The package with dependencies is quite small < 39kb itself.
@@ -144,7 +145,58 @@ as_base64 = my_file.to_base64()
144
145
  as_json = my_file.to_json()
145
146
  ```
146
147
 
147
- ### Working with VideoFiles.
148
+ ## Working with Collections of Files
149
+
150
+ ### MediaList
151
+ A flexible list that can handle multiple media files with type safety:
152
+
153
+ ```python
154
+ from media_toolkit import MediaList, AudioFile
155
+
156
+ # Create a list that only accepts AudioFiles
157
+ audio_list = MediaList[AudioFile]()
158
+
159
+ # Add files to the list
160
+ audio_list.append("path/to/audio.mp3")
161
+ audio_list.extend(["url1", "url2"])
162
+
163
+ # Process all files
164
+ for audio in audio_list:
165
+ print(audio.file_size())
166
+
167
+ # Convert all files to base64
168
+ base64_files = audio_list.to_base64()
169
+ ```
170
+
171
+ ### MediaDict
172
+ A dictionary for organizing media files with keys:
173
+
174
+ ```python
175
+ from media_toolkit import MediaDict, ImageFile
176
+
177
+ # Create a dictionary that only accepts ImageFiles
178
+ image_dict = MediaDict[ImageFile]()
179
+
180
+ # Add files with keys
181
+ image_dict["profile"] = "path/to/profile.jpg"
182
+ image_dict["banner"] = "https://example.com/banner.png"
183
+
184
+ # Process files
185
+ for key, image in image_dict.items():
186
+ print(f"{key}: {image.file_size()}")
187
+
188
+ # Convert to JSON
189
+ json_data = image_dict.to_json()
190
+ ```
191
+
192
+ Both `MediaList` and `MediaDict` support:
193
+ - Type safety with generic types (e.g., `MediaList[AudioFile]`)
194
+ - Lazy loading of files
195
+ - Batch processing
196
+ - Common operations (to_base64, to_bytes, etc.)
197
+ - Nested structures (MediaDict inside MediaList and vice versa)
198
+
199
+ ### Working with VideoFiles
148
200
 
149
201
  The VideoFiles wrap the famous [vidgear](https://abhitronix.github.io/vidgear/latest/) package as well as [pydub](https://github.com/jiaaro/pydub).
150
202
  VideoFiles support extra methods like audio extraction, combining video and audio.
@@ -193,7 +245,7 @@ You can use the files in fastapi and transform the starlette upload file to a Me
193
245
  ```python
194
246
  @app.post("/upload")
195
247
  async def upload_file(file: UploadFile = File(...)):
196
- mf = ImageFile().from_starlette_upload_file(file)
248
+ mf = ImageFile().from_any(file)
197
249
  return {"filename": file.filename}
198
250
  ```
199
251
 
@@ -211,16 +263,12 @@ my_files = {
211
263
  response = httpx.Client().post(url, files=my_files)
212
264
  ```
213
265
 
214
-
215
266
  # How it works
216
267
 
217
268
  If media-file is instantiated with ```from_*``` it converts it to an intermediate representation.
218
269
  The ```to_*``` methods then convert it to the desired format.
219
270
 
220
- Currently the intermediate representation is supported in memory with (BytesIO).
221
-
271
+ Currently the intermediate representation is supported in memory with (BytesIO) or on disk with temporary files.
222
272
 
223
273
  # ToDo:
224
-
225
- - [x] additionally support tempfile backend instead of working bytesio memory mode only.
226
274
  - [x] decreasing redundancies for _file_info() method
@@ -1,9 +1,9 @@
1
1
 
2
- <h1 align="center" style="margin-top:-25px">MediaToolkit</h1>
2
+ <h1 align="center" style="margin-top:-25px">MediaToolkit</h1>
3
3
  <p align="center">
4
4
  <img align="center" src="docs/media-file-icon.png" height="200" />
5
5
  </p>
6
- <h3 align="center" style="margin-top:-10px">Web-ready standardized file processing and serialization</h3>
6
+ <h3 align="center" style="margin-top:-10px">Web-ready standardized file processing and serialization</h3>
7
7
 
8
8
 
9
9
  # Features
@@ -14,7 +14,7 @@ Especially useful for code that works with multiple file types like images, audi
14
14
  Load and convert from and to common data types:
15
15
  - numpy arrays
16
16
  - file paths
17
- - bytes,
17
+ - bytes
18
18
  - base64
19
19
  - json
20
20
  - urls
@@ -42,6 +42,7 @@ pip install media-toolkit[VideoFile] # or [AudioFile, VideoFile, ...]
42
42
  # install from github for newest release
43
43
  pip install git+git://github.com/SocAIty/media-toolkit
44
44
  ```
45
+
45
46
  The package checks if you have missing dependencies for certain file types while using.
46
47
  Use the ```--no-deps``` flag for a minimal tiny pure python installation.
47
48
  The package with dependencies is quite small < 39kb itself.
@@ -94,7 +95,58 @@ as_base64 = my_file.to_base64()
94
95
  as_json = my_file.to_json()
95
96
  ```
96
97
 
97
- ### Working with VideoFiles.
98
+ ## Working with Collections of Files
99
+
100
+ ### MediaList
101
+ A flexible list that can handle multiple media files with type safety:
102
+
103
+ ```python
104
+ from media_toolkit import MediaList, AudioFile
105
+
106
+ # Create a list that only accepts AudioFiles
107
+ audio_list = MediaList[AudioFile]()
108
+
109
+ # Add files to the list
110
+ audio_list.append("path/to/audio.mp3")
111
+ audio_list.extend(["url1", "url2"])
112
+
113
+ # Process all files
114
+ for audio in audio_list:
115
+ print(audio.file_size())
116
+
117
+ # Convert all files to base64
118
+ base64_files = audio_list.to_base64()
119
+ ```
120
+
121
+ ### MediaDict
122
+ A dictionary for organizing media files with keys:
123
+
124
+ ```python
125
+ from media_toolkit import MediaDict, ImageFile
126
+
127
+ # Create a dictionary that only accepts ImageFiles
128
+ image_dict = MediaDict[ImageFile]()
129
+
130
+ # Add files with keys
131
+ image_dict["profile"] = "path/to/profile.jpg"
132
+ image_dict["banner"] = "https://example.com/banner.png"
133
+
134
+ # Process files
135
+ for key, image in image_dict.items():
136
+ print(f"{key}: {image.file_size()}")
137
+
138
+ # Convert to JSON
139
+ json_data = image_dict.to_json()
140
+ ```
141
+
142
+ Both `MediaList` and `MediaDict` support:
143
+ - Type safety with generic types (e.g., `MediaList[AudioFile]`)
144
+ - Lazy loading of files
145
+ - Batch processing
146
+ - Common operations (to_base64, to_bytes, etc.)
147
+ - Nested structures (MediaDict inside MediaList and vice versa)
148
+
149
+ ### Working with VideoFiles
98
150
 
99
151
  The VideoFiles wrap the famous [vidgear](https://abhitronix.github.io/vidgear/latest/) package as well as [pydub](https://github.com/jiaaro/pydub).
100
152
  VideoFiles support extra methods like audio extraction, combining video and audio.
@@ -143,7 +195,7 @@ You can use the files in fastapi and transform the starlette upload file to a Me
143
195
  ```python
144
196
  @app.post("/upload")
145
197
  async def upload_file(file: UploadFile = File(...)):
146
- mf = ImageFile().from_starlette_upload_file(file)
198
+ mf = ImageFile().from_any(file)
147
199
  return {"filename": file.filename}
148
200
  ```
149
201
 
@@ -161,16 +213,12 @@ my_files = {
161
213
  response = httpx.Client().post(url, files=my_files)
162
214
  ```
163
215
 
164
-
165
216
  # How it works
166
217
 
167
218
  If media-file is instantiated with ```from_*``` it converts it to an intermediate representation.
168
219
  The ```to_*``` methods then convert it to the desired format.
169
220
 
170
- Currently the intermediate representation is supported in memory with (BytesIO).
171
-
221
+ Currently the intermediate representation is supported in memory with (BytesIO) or on disk with temporary files.
172
222
 
173
223
  # ToDo:
174
-
175
- - [x] additionally support tempfile backend instead of working bytesio memory mode only.
176
- - [x] decreasing redundancies for _file_info() method
224
+ - [x] decreasing redundancies for _file_info() method
@@ -0,0 +1,4 @@
1
+ from media_toolkit.core import MediaFile, ImageFile, VideoFile, AudioFile, MediaList, MediaDict
2
+ from media_toolkit.utils.file_conversion import media_from_file, media_from_any
3
+
4
+ __all__ = ["MediaFile", "ImageFile", "VideoFile", "AudioFile", "MediaList", "MediaDict", "media_from_file", "media_from_any"]
@@ -0,0 +1,86 @@
1
+ from abc import ABC, abstractmethod
2
+ import io
3
+ from typing import Dict, Any, Optional
4
+
5
+
6
+ class IMediaFile(ABC):
7
+ """
8
+ Abstract base interface defining the core contract for media file handling
9
+ in the Media Toolkit ecosystem.
10
+ """
11
+ @abstractmethod
12
+ def from_any(self, data: Any, allow_reads_from_disk: bool = True) -> 'IMediaFile':
13
+ """
14
+ Load file content from various input sources.
15
+
16
+ Args:
17
+ data: Input source (bytes, file path, URL, base64, etc.)
18
+ allow_reads_from_disk: Flag to control disk file reading
19
+
20
+ Returns:
21
+ Self, for method chaining
22
+ """
23
+ pass
24
+
25
+ @abstractmethod
26
+ def to_bytes(self) -> bytes:
27
+ """Convert file content to raw bytes."""
28
+ pass
29
+
30
+ @abstractmethod
31
+ def to_base64(self) -> str:
32
+ """Encode file content to base64."""
33
+ pass
34
+
35
+ @abstractmethod
36
+ def to_bytes_io(self) -> io.BytesIO:
37
+ """Convert file content to BytesIO object."""
38
+ pass
39
+
40
+ @abstractmethod
41
+ def to_httpx_send_able_tuple(self) -> tuple:
42
+ """
43
+ Prepare file for HTTP transmission.
44
+
45
+ Returns:
46
+ Tuple of (filename, content, content_type)
47
+ """
48
+ pass
49
+
50
+ @abstractmethod
51
+ def save(self, path: Optional[str] = None):
52
+ """
53
+ Save file to specified path.
54
+
55
+ Args:
56
+ path: Destination path. Uses current directory if None.
57
+ """
58
+ pass
59
+
60
+ @abstractmethod
61
+ def file_size(self, unit: str = "bytes") -> float:
62
+ """
63
+ Get file size in specified units.
64
+
65
+ Args:
66
+ unit: Size unit (bytes, kb, mb, gb)
67
+
68
+ Returns:
69
+ File size in specified unit
70
+ """
71
+ pass
72
+
73
+ @abstractmethod
74
+ def to_json(self) -> Dict[str, Any]:
75
+ """
76
+ Serialize file to JSON-compatible dictionary.
77
+
78
+ Returns:
79
+ Dictionary representation of the file
80
+ """
81
+ pass
82
+
83
+ @abstractmethod
84
+ def __sizeof__(self):
85
+ """Get the size of the file in bytes."""
86
+ pass
@@ -0,0 +1,353 @@
1
+ import io
2
+ import uuid
3
+ from typing import List, Union, Optional, Any, Dict, TypeVar, Generic
4
+ from media_toolkit.core.IMediaFile import IMediaFile
5
+ from media_toolkit.core.media_file import MediaFile
6
+ from media_toolkit.core.MediaList import MediaList
7
+
8
+ T = TypeVar('T', bound=IMediaFile)
9
+
10
+
11
+ class MediaDict(IMediaFile, Generic[T]):
12
+ """
13
+ A flexible media file dictionary that handles multiple file types
14
+ and sources with configurable loading behaviors.
15
+
16
+ Supports:
17
+ - Multiple MediaFile types as dictionary values
18
+ - Batch media processing
19
+ - Generic type restrictions (e.g. MediaDict[AudioFile])
20
+ """
21
+ def __init__(
22
+ self,
23
+ files: Optional[Dict[str, Union[str, T, MediaList[T], 'MediaDict[T]']]] = None,
24
+ download_files: bool = True,
25
+ read_system_files: bool = True,
26
+ file_name: str = "MediaDict",
27
+ use_temp_file: bool = False,
28
+ temp_dir: str = None
29
+ ):
30
+ """
31
+ Initialize MediaDict with optional files and configuration.
32
+
33
+ Args:
34
+ files: Dictionary of files with keys as identifiers
35
+ download_files: Flag if files provided as URLs are downloaded and converted
36
+ read_system_files: Flag if files provided as paths are read and converted
37
+ file_name: Name of the media dictionary
38
+ use_temp_file: Flag to use temp file for file processing
39
+ temp_dir: Temp directory path for file processing
40
+ """
41
+ self.file_name = file_name
42
+ self.use_temp_file = use_temp_file
43
+ self.temp_dir = temp_dir
44
+ self.download_files = download_files
45
+ self.read_system_files = read_system_files
46
+ self._media_files: Dict[str, Union[str, T, MediaList[T]]] = {}
47
+
48
+ if files:
49
+ self.update(files)
50
+
51
+ @staticmethod
52
+ def _is_empty_file(file: Any) -> bool:
53
+ """ Check if file has any content. """
54
+ if isinstance(file, list) and all(MediaDict._is_empty_file(item) for item in file):
55
+ return True
56
+ return file is None or (hasattr(file, '__len__') and len(file) == 0)
57
+
58
+ def _process_file(
59
+ self,
60
+ file: Union[str, T, MediaList[T], 'MediaDict[T]']
61
+ ) -> Union[str, T, MediaList[T], 'MediaDict[T]']:
62
+ """
63
+ Process a single file based on configuration.
64
+
65
+ Args:
66
+ file: File to process (URL, path, MediaFile, MediaList)
67
+ Returns:
68
+ Processed file (MediaFile, MediaList, or original str)
69
+ """
70
+ if isinstance(file, (IMediaFile, MediaList, MediaDict)):
71
+ return file
72
+
73
+ # check if is empty
74
+ if MediaDict._is_empty_file(file):
75
+ return file
76
+
77
+ # perform conversion
78
+ if isinstance(file, str):
79
+ if MediaFile._is_url(file):
80
+ if not self.download_files:
81
+ return file
82
+ return MediaFile(use_temp_file=self.use_temp_file, temp_dir=self.temp_dir).from_url(file)
83
+
84
+ if MediaFile._is_valid_file_path(file):
85
+ if not self.read_system_files:
86
+ return file
87
+ return MediaFile(use_temp_file=self.use_temp_file, temp_dir=self.temp_dir).from_file(file)
88
+
89
+ if MediaFile._is_file_model(file):
90
+ from media_toolkit.utils import media_from_FileModel
91
+ return media_from_FileModel(file, allow_reads_from_disk=self.read_system_files)
92
+
93
+ if isinstance(file, list):
94
+ return MediaList[T](
95
+ files=file,
96
+ download_files=self.download_files,
97
+ read_system_files=self.read_system_files,
98
+ use_temp_file=self.use_temp_file,
99
+ temp_dir=self.temp_dir
100
+ )
101
+
102
+ if isinstance(file, dict):
103
+ return MediaDict[T](
104
+ files=file,
105
+ download_files=self.download_files,
106
+ read_system_files=self.read_system_files,
107
+ use_temp_file=self.use_temp_file,
108
+ temp_dir=self.temp_dir
109
+ )
110
+
111
+ return MediaFile(use_temp_file=self.use_temp_file, temp_dir=self.temp_dir).from_any(file)
112
+
113
+ def from_any(
114
+ self,
115
+ data: Union[Dict[str, Union[str, T, MediaList[T]]], Any]
116
+ ) -> 'MediaDict[T]':
117
+ """
118
+ Load files from a dictionary of files.
119
+
120
+ Args:
121
+ data: Dictionary of files to load
122
+ Returns:
123
+ Self, for method chaining
124
+ """
125
+ self.update(data)
126
+ return self
127
+
128
+ def get_processable_files(
129
+ self,
130
+ ignore_all_potential_errors: bool = False,
131
+ raise_exception: bool = True,
132
+ silent: bool = False
133
+ ) -> 'MediaDict[T]':
134
+ """
135
+ Validate that all files can be processed for batch operations.
136
+
137
+ Args:
138
+ ignore_all_potential_errors: Ignore processing errors
139
+ raise_exception: Raise exceptions for unprocessable files
140
+ silent: Suppress error messages
141
+ Returns:
142
+ Dictionary of processable files
143
+ """
144
+ if ignore_all_potential_errors:
145
+ return self
146
+
147
+ processable_files = {
148
+ key: file for key, file in self._media_files.items()
149
+ if isinstance(file, (IMediaFile, MediaList))
150
+ }
151
+
152
+ if len(processable_files) != len(self._media_files):
153
+ not_processable_file_names = [
154
+ str(key) for key, file in self._media_files.items()
155
+ if file not in processable_files.values()
156
+ ]
157
+ message = (
158
+ f"Files not processed: {not_processable_file_names}. "
159
+ f"Check configuration (download_files={self.download_files}, "
160
+ f"read_system_files={self.read_system_files})"
161
+ )
162
+
163
+ if raise_exception:
164
+ raise ValueError(message)
165
+ if not silent:
166
+ print(message)
167
+
168
+ return self._shallow_copy_with_settings(processable_files)
169
+
170
+ return self
171
+
172
+ def _shallow_copy_with_settings(self, data: dict | None = None) -> 'MediaDict[T]':
173
+ """
174
+ Creates a new MediaDict with the same settings but shallow copies the media files dictionary.
175
+ This avoids re-reading all files when creating a copy.
176
+ """
177
+ md = MediaDict[T](
178
+ file_name=self.file_name, download_files=self.download_files,
179
+ read_system_files=self.read_system_files, use_temp_file=self.use_temp_file, temp_dir=self.temp_dir
180
+ )
181
+ md._media_files = data
182
+ return md
183
+
184
+ def get_url_files(self) -> Union['MediaDict[T]', dict]:
185
+ """
186
+ Get all non-processed files that are URLs.
187
+
188
+ Returns:
189
+ Dictionary of URL files
190
+ """
191
+ if self.download_files:
192
+ return {}
193
+
194
+ return self._shallow_copy_with_settings({
195
+ key: file for key, file in self._media_files.items()
196
+ if isinstance(file, str) and MediaFile._is_url(file)
197
+ })
198
+
199
+ def get_file_path_files(self) -> Union['MediaDict[T]', dict]:
200
+ """
201
+ Get all non-processed files that are file paths.
202
+
203
+ Returns:
204
+ Dictionary of file path files
205
+ """
206
+ if self.read_system_files:
207
+ return {}
208
+ return self._shallow_copy_with_settings({
209
+ key: file for key, file in self._media_files.items()
210
+ if isinstance(file, str) and MediaFile._is_valid_file_path(file)
211
+ })
212
+
213
+ def to_base64(self) -> Dict[str, str]:
214
+ """Convert all processable files to base64."""
215
+ return {
216
+ key: file.to_base64()
217
+ for key, file in self.get_processable_files(raise_exception=False).items()
218
+ }
219
+
220
+ def to_bytes_io(self) -> Dict[str, io.BytesIO]:
221
+ """Convert all processable files to BytesIO."""
222
+ return {
223
+ key: file.to_bytes_io()
224
+ for key, file in self.get_processable_files(raise_exception=False).items()
225
+ }
226
+
227
+ def file_size(self, unit: str = "bytes") -> float:
228
+ """Calculate total file size."""
229
+ return sum(
230
+ file.file_size(unit)
231
+ for file in self.get_processable_files(raise_exception=False).values()
232
+ )
233
+
234
+ def to_json(self) -> Dict[str, Any]:
235
+ """Convert files to JSON representation."""
236
+ files = self.get_processable_files(ignore_all_potential_errors=True)
237
+ return {
238
+ key: (file.to_json() if isinstance(file, (IMediaFile, MediaList)) else file)
239
+ for key, file in files.items()
240
+ }
241
+
242
+ def to_bytes(self) -> Dict[str, bytes]:
243
+ """Convert all processable files to bytes."""
244
+ return {
245
+ key: file.to_bytes()
246
+ for key, file in self.get_processable_files(raise_exception=False).items()
247
+ }
248
+
249
+ def to_httpx_send_able_tuple(self) -> List[tuple] | dict:
250
+ """
251
+ Convert files to httpx-send-able format.
252
+
253
+ Args:
254
+ param_name: Optional parameter name for API endpoint
255
+ Returns:
256
+ List of tuples for httpx file transmission
257
+ """
258
+ files = self.get_processable_files(raise_exception=False, silent=True)
259
+
260
+ ret = []
261
+ for k, file in files.items():
262
+ if isinstance(file, MediaList):
263
+ ret.extend(file.to_httpx_sendable_tuple(k))
264
+ elif isinstance(file, MediaDict):
265
+ fls = file.to_httpx_sendable_tuple()
266
+ if isinstance(fls, dict):
267
+ ret.append((k, fls))
268
+ else:
269
+ ret.extend(fls)
270
+ else:
271
+ ret.append((k, file.to_httpx_send_able_tuple()))
272
+
273
+ if len(ret) == 1:
274
+ return {ret[0][0]: ret[0][1]}
275
+ return ret
276
+
277
+ def save(self, directory: Optional[str] = None):
278
+ """
279
+ Save all processable files to a specified directory.
280
+
281
+ Args:
282
+ directory: Target directory (uses current directory if None)
283
+ """
284
+ import os
285
+ directory = directory or os.path.curdir
286
+ os.makedirs(directory, exist_ok=True)
287
+
288
+ for key, file in self.get_processable_files(raise_exception=False).items():
289
+ file.save(directory)
290
+
291
+ def __getitem__(self, key: str):
292
+ """Allow dictionary-style access."""
293
+ return self._media_files[key]
294
+
295
+ def __setitem__(self, key: str, value: Union[str, T, MediaList[T]]):
296
+ """Allow dictionary-style assignment with processing."""
297
+ self._media_files[key] = self._process_file(value)
298
+
299
+ def __delitem__(self, key: str):
300
+ """Allow dictionary-style deletion."""
301
+ del self._media_files[key]
302
+
303
+ def __iter__(self):
304
+ """Make the class iterable."""
305
+ return iter(self._media_files)
306
+
307
+ def __len__(self):
308
+ """Return the number of files in the dictionary."""
309
+ return len(self._media_files)
310
+
311
+ def __contains__(self, key: str):
312
+ """Check if a key exists in the dictionary."""
313
+ return key in self._media_files
314
+
315
+ def keys(self):
316
+ """Return dictionary keys."""
317
+ return self._media_files.keys()
318
+
319
+ def values(self):
320
+ """Return dictionary values."""
321
+ return self._media_files.values()
322
+
323
+ def items(self):
324
+ """Return dictionary items."""
325
+ return self._media_files.items()
326
+
327
+ def update(self, files: Union['MediaDict[T]', Dict[str, Union[str, T, MediaList[T]]]]):
328
+ """
329
+ Update the dictionary with new files.
330
+
331
+ Args:
332
+ files: Dictionary of files to add or update
333
+ """
334
+ if files is None:
335
+ return
336
+
337
+ if not isinstance(files, dict) and not isinstance(files, MediaDict):
338
+ files = {str(uuid.uuid4()): files}
339
+
340
+ for key, file in files.items():
341
+ self[key] = self._process_file(file)
342
+
343
+ def __sizeof__(self):
344
+ """Returns the memory size of the instance + actual file/buffer size."""
345
+ size = super().__sizeof__() + self.file_size("bytes")
346
+ return size
347
+
348
+ def to_dict(self) -> Dict[str, Union[str, T, MediaList[T]]]:
349
+ """Convert MediaDict to a standard dictionary."""
350
+ return {
351
+ key: (file.to_dict() if isinstance(file, MediaDict) else file)
352
+ for key, file in self._media_files.items()
353
+ }