diff --git a/homework_04/main.py b/homework_04/main.py new file mode 100644 index 0000000..e9b9687 --- /dev/null +++ b/homework_04/main.py @@ -0,0 +1,184 @@ + +# homework_04/media.py +# абстракции — через NotImplementedError + +class Storage: + """Абстракция хранилищ.""" + def save(self, path, bytes_or_stream): + raise NotImplementedError + def load(self, path): + raise NotImplementedError + def delete(self, path): + raise NotImplementedError + def exists(self, path): + raise NotImplementedError + +class LocalStorage(Storage): + """Заглушки для локального диска.""" + def save(self, path, bytes_or_stream): + # открыть файл и записать bytes_or_stream + pass + def load(self, path): + # вернуть байты/поток + return None + def delete(self, path): + # удалить файл + pass + def exists(self, path): + return True + +class S3Storage(Storage): + """Заглушки для s3-like.""" + def __init__(self, bucket): + self.bucket = bucket + def save(self, path, bytes_or_stream): + # загрузить объект в bucket/path + pass + def load(self, path): + return None + def delete(self, path): + pass + def exists(self, path): + return True + +class MediaFile: + """Базовый медиа-файл.""" + def __init__(self, name, size=0, created_at=None, owner=None, + storage=None, path=None, meta=None): + self.name = name + self.size = size + self.created_at = created_at + self.owner = owner + self.storage = storage or LocalStorage() + self.path = path or name + self.meta = meta or {} # общие метаданные + + # --- CRUD --- + def save(self, bytes_or_stream=None): + """Сохранить контент и метаданные в storage по self.path.""" + self.storage.save(self.path, bytes_or_stream) + def load(self): + """Загрузить контент из storage.""" + return self.storage.load(self.path) + def delete(self): + """Удалить из storage.""" + self.storage.delete(self.path) + + # --- Бизнес-операции (заглушки) --- + def convert(self, **params): + """Сконвертировать в другой формат/профиль, вернуть новый объект.""" + raise NotImplementedError + def extract_features(self, **params): + """Извлечь фичи (превью, спектр, EXIF...), вернуть dict.""" + raise NotImplementedError + def validate(self): + """Проверить согласованность метаданных/контента.""" + return True + +class AudioFile(MediaFile): + """Аудио-метаданные: bitrate, sample_rate, channels, codec...""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.meta.setdefault("bitrate", None) + self.meta.setdefault("sample_rate", None) + self.meta.setdefault("channels", None) + self.meta.setdefault("codec", None) + + def convert(self, target_codec=None, bitrate=None, sample_rate=None, **_): + # вернёт новый AudioFile с обновлёнными метаданными/путём + new_name = self.name + ".conv" + return AudioFile(new_name, storage=self.storage, path=new_name, + meta={ + "bitrate": bitrate or self.meta["bitrate"], + "sample_rate": sample_rate or self.meta["sample_rate"], + "channels": self.meta["channels"], + "codec": target_codec or self.meta["codec"], + }) + + def extract_features(self, **_): + # вернёт, например, длительность и спектральные признаки + return {"duration_sec": None, "mfcc": None} + +class VideoFile(MediaFile): + """Видео-метаданные: resolution, fps, vcodec, acodec, duration...""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.meta.setdefault("resolution", None) + self.meta.setdefault("fps", None) + self.meta.setdefault("vcodec", None) + self.meta.setdefault("acodec", None) + self.meta.setdefault("duration", None) + + def convert(self, resolution=None, fps=None, vcodec=None, **_): + new_name = self.name + ".conv" + return VideoFile(new_name, storage=self.storage, path=new_name, + meta={ + "resolution": resolution or self.meta["resolution"], + "fps": fps or self.meta["fps"], + "vcodec": vcodec or self.meta["vcodec"], + "acodec": self.meta["acodec"], + "duration": self.meta["duration"], + }) + + def extract_features(self, **_): + # ключевые кадры, превью, цветовая гистограмма + return {"thumbnails": None, "keyframes": None, "color_hist": None} + +class PhotoFile(MediaFile): + """Фото-метаданные: width, height, exif, colorspace...""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.meta.setdefault("width", None) + self.meta.setdefault("height", None) + self.meta.setdefault("exif", {}) + self.meta.setdefault("colorspace", None) + + def convert(self, colorspace=None, size=None, **_): + new_name = self.name + ".conv" + new_meta = dict(self.meta) + if colorspace is not None: + new_meta["colorspace"] = colorspace + if size is not None: + new_meta["width"], new_meta["height"] = size + return PhotoFile(new_name, storage=self.storage, path=new_name, meta=new_meta) + + def extract_features(self, **_): + # гистограммы, лица, EXIF + return {"faces": None, "histogram": None, "exif": self.meta.get("exif", {})} + +# ---- Примеры использования ---- + +def examples(): + local = LocalStorage() + s3 = S3Storage(bucket="media") + + # Создание + a = AudioFile("song.mp3", storage=local, meta={"bitrate": 320, "sample_rate": 44100, "channels": 2, "codec": "mp3"}) + v = VideoFile("clip.mp4", storage=s3, meta={"resolution": "1920x1080", "fps": 30, "vcodec": "h264"}) + p = PhotoFile("pic.jpg", storage=local, meta={"width": 4000, "height": 3000, "colorspace": "sRGB"}) + + # Save / Load / Delete + a.save(bytes_or_stream=None) # записать контент + data = a.load() # прочитать + a.delete() # удалить + + # Действия + a2 = a.convert(target_codec="aac", bitrate=256) + feats_a = a.extract_features() + + v2 = v.convert(resolution="1280x720", fps=24) + feats_v = v.extract_features() + + p2 = p.convert(colorspace="AdobeRGB", size=(2048, 1536)) + feats_p = p.extract_features() + + # Работа с разными хранилищами прозрачно: + p2.save() + v2.save() + + + + + +if __name__ == "__main__": + examples() \ No newline at end of file