Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions homework_04/main.py
Original file line number Diff line number Diff line change
@@ -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()