{"id":13557782,"url":"https://github.com/eseifert/madam","last_synced_at":"2026-03-06T21:08:30.670Z","repository":{"id":57439077,"uuid":"64922454","full_name":"eseifert/madam","owner":"eseifert","description":"Media Asset Management for Python","archived":false,"fork":false,"pushed_at":"2022-10-29T15:24:26.000Z","size":2877,"stargazers_count":26,"open_issues_count":12,"forks_count":7,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-22T08:16:06.978Z","etag":null,"topics":["hacktoberfest","hacktoberfest2022","media-asset-management","media-management","media-processing","python","python3"],"latest_commit_sha":null,"homepage":"https://madam.readthedocs.io","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eseifert.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.AGPL","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-08-04T09:38:27.000Z","updated_at":"2025-02-24T15:19:13.000Z","dependencies_parsed_at":"2022-09-26T17:11:36.332Z","dependency_job_id":null,"html_url":"https://github.com/eseifert/madam","commit_stats":null,"previous_names":[],"tags_count":33,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eseifert%2Fmadam","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eseifert%2Fmadam/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eseifert%2Fmadam/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eseifert%2Fmadam/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eseifert","download_url":"https://codeload.github.com/eseifert/madam/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248083198,"owners_count":21045056,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest","hacktoberfest2022","media-asset-management","media-management","media-processing","python","python3"],"created_at":"2024-08-01T12:04:32.602Z","updated_at":"2026-03-06T21:08:30.661Z","avatar_url":"https://github.com/eseifert.png","language":"Python","readme":"MADAM\n#####\n\nMultimedia Advanced Digital Asset Management\n\n|ci-badge|_ |codecov-badge|_ |pypi-badge|_ |readthedocs-badge|_\n\n.. |ci-badge| image:: https://github.com/eseifert/madam/actions/workflows/ci.yml/badge.svg?\n.. _ci-badge: https://github.com/eseifert/madam/actions/workflows/ci.yml\n.. |codecov-badge| image:: https://codecov.io/gh/eseifert/madam/branch/master/graph/badge.svg?token=x0aF4xnSz5\n.. _codecov-badge: https://codecov.io/gh/eseifert/madam\n.. |pypi-badge| image:: https://img.shields.io/pypi/v/madam.svg?\n.. _pypi-badge: https://pypi.python.org/pypi/MADAM\n.. |readthedocs-badge| image:: https://readthedocs.org/projects/madam/badge/?version=latest\n.. _readthedocs-badge: https://madam.readthedocs.io/en/latest/?badge=latest\n\nMADAM is a digital asset management library.  It aims to facilitate the\nhandling of image, audio, and video files by helping out with several tasks,\nlike storing, organizing, and transforming asset data.\n\n.. quickstart_start\n\nInstallation\n============\nMADAM requires Python 3.11 or later and the following system packages:\n\n    - ``FFmpeg`` \u003e=3.3 for audio and video processing\n\nAfter you have installed these, MADAM can be installed from PyPI::\n\n    pip install madam\n\nOptional extras enable additional format support::\n\n    pip install \"madam[analysis]\"  # SSIMULACRA2 quality optimization\n    pip install \"madam[heif]\"      # HEIC/HEIF images (requires pillow-heif)\n    pip install \"madam[optimize]\"  # Zopfli PNG compression\n    pip install \"madam[pdf]\"       # PDF rasterization (requires poppler)\n    pip install \"madam[raw]\"       # Raw camera formats (requires LibRaw)\n    pip install \"madam[all]\"       # All of the above\n\n\nUsage\n=====\n\nInitialization\n--------------\n\n.. code:: pycon\n\n    \u003e\u003e\u003e from madam import Madam\n    \u003e\u003e\u003e madam = Madam()\n\nTo set format-specific defaults, pass a configuration dictionary:\n\n.. code:: pycon\n\n    \u003e\u003e\u003e config = {\n    ...     'image/jpeg': {'quality': 85},\n    ...     'image/webp': {'quality': 80, 'method': 6},\n    ... }\n    \u003e\u003e\u003e madam = Madam(config)\n\n\nReading and inspecting assets\n------------------------------\n\n.. code:: pycon\n\n    \u003e\u003e\u003e with open('path/to/photo.jpg', 'rb') as f:\n    ...     asset = madam.read(f)\n    \u003e\u003e\u003e asset.mime_type\n    'image/jpeg'\n    \u003e\u003e\u003e asset.width\n    4000\n    \u003e\u003e\u003e asset.height\n    3000\n    \u003e\u003e\u003e asset.color_space\n    'RGB'\n    \u003e\u003e\u003e asset.content_id   # SHA-256 of the essence bytes\n    'a3f5c8...'\n\n\nReading embedded metadata\n--------------------------\n\n``madam.read()`` automatically extracts EXIF, IPTC, and XMP metadata:\n\n.. code:: pycon\n\n    \u003e\u003e\u003e exif = asset.metadata.get('exif', {})\n    \u003e\u003e\u003e exif.get('camera.model')\n    'Canon EOS 5D Mark III'\n    \u003e\u003e\u003e exif.get('focal_length')\n    85.0\n    \u003e\u003e\u003e exif.get('datetime_original')\n    datetime.datetime(2024, 6, 15, 10, 30, 0)\n    \u003e\u003e\u003e asset.created_at    # unified ISO 8601 timestamp\n    '2024-06-15T10:30:00'\n\n\nResizing images\n---------------\n\nOperators are configured once and then applied to any number of assets:\n\n.. code:: pycon\n\n    \u003e\u003e\u003e from madam.image import ResizeMode\n    \u003e\u003e\u003e processor = madam.get_processor(asset)\n    \u003e\u003e\u003e make_thumbnail = processor.resize(width=200, height=200, mode=ResizeMode.FIT)\n    \u003e\u003e\u003e thumbnail = make_thumbnail(asset)\n    \u003e\u003e\u003e thumbnail.width\n    200\n\n\nApplying image effects\n-----------------------\n\n.. code:: pycon\n\n    \u003e\u003e\u003e # Brighten by 20 %, add a warm tint, then apply a vignette\n    \u003e\u003e\u003e brighten = processor.adjust_brightness(factor=1.2)\n    \u003e\u003e\u003e add_tint = processor.tint(color=(255, 180, 80), opacity=0.2)\n    \u003e\u003e\u003e add_vignette = processor.vignette(strength=0.4)\n    \u003e\u003e\u003e result = add_vignette(add_tint(brighten(asset)))\n\n\nConverting format and saving\n-----------------------------\n\n.. code:: pycon\n\n    \u003e\u003e\u003e convert_to_webp = processor.convert(mime_type='image/webp')\n    \u003e\u003e\u003e webp_asset = convert_to_webp(asset)\n    \u003e\u003e\u003e with open('path/to/output.webp', 'wb') as f:\n    ...     madam.write(webp_asset, f)\n\n\nBuilding a pipeline\n--------------------\n\nUse ``Pipeline`` to chain operators and process batches:\n\n.. code:: pycon\n\n    \u003e\u003e\u003e from madam.core import Pipeline\n    \u003e\u003e\u003e pipeline = Pipeline()\n    \u003e\u003e\u003e pipeline.add(processor.resize(width=800, height=600, mode=ResizeMode.FIT))\n    \u003e\u003e\u003e pipeline.add(processor.sharpen(radius=2, percent=120))\n    \u003e\u003e\u003e pipeline.add(processor.convert(mime_type='image/jpeg'))\n    \u003e\u003e\u003e for processed in pipeline.process(asset_a, asset_b, asset_c):\n    ...     with open(f'{processed.content_id}.jpg', 'wb') as f:\n    ...         f.write(processed.essence.read())\n\n\nAudio processing\n-----------------\n\n.. code:: pycon\n\n    \u003e\u003e\u003e from madam.audio import AudioCodec\n    \u003e\u003e\u003e audio_processor = madam.get_processor(audio)\n    \u003e\u003e\u003e with open('track.mp3', 'rb') as f:\n    ...     audio = madam.read(f)\n    \u003e\u003e\u003e audio.duration          # seconds\n    243.7\n    \u003e\u003e\u003e audio.metadata['audio']['codec']\n    'mp3'\n    \u003e\u003e\u003e # Normalize loudness to EBU R128 broadcast standard (−23 LUFS)\n    \u003e\u003e\u003e normalize = audio_processor.normalize_audio(target_lufs=-23.0)\n    \u003e\u003e\u003e loud_normalized = normalize(audio)\n    \u003e\u003e\u003e # Convert to Opus\n    \u003e\u003e\u003e to_opus = audio_processor.convert(\n    ...     mime_type='audio/ogg',\n    ...     audio={'codec': AudioCodec.OPUS, 'bitrate': 128},\n    ... )\n    \u003e\u003e\u003e opus_asset = to_opus(audio)\n    \u003e\u003e\u003e with open('track.opus', 'wb') as f:\n    ...     madam.write(opus_asset, f)\n\n\nVideo processing\n-----------------\n\n.. code:: pycon\n\n    \u003e\u003e\u003e from madam.video import VideoCodec, concatenate\n    \u003e\u003e\u003e video_processor = madam.get_processor(video)\n    \u003e\u003e\u003e with open('clip.mp4', 'rb') as f:\n    ...     video = madam.read(f)\n    \u003e\u003e\u003e video.duration\n    30.0\n    \u003e\u003e\u003e # Trim to the first 10 seconds\n    \u003e\u003e\u003e trim = video_processor.trim(start=0.0, duration=10.0)\n    \u003e\u003e\u003e short_clip = trim(video)\n    \u003e\u003e\u003e # Create a 4× timelapse\n    \u003e\u003e\u003e speed_up = video_processor.set_speed(factor=4.0)\n    \u003e\u003e\u003e timelapse = speed_up(video)\n    \u003e\u003e\u003e # Concatenate clips\n    \u003e\u003e\u003e combined = concatenate([clip_a, clip_b], mime_type='video/mp4')\n    \u003e\u003e\u003e # Extract a thumbnail sprite sheet (5 columns × 4 rows, 160×90 px each)\n    \u003e\u003e\u003e make_sprite = video_processor.thumbnail_sprite(\n    ...     columns=5, rows=4, thumb_width=160, thumb_height=90\n    ... )\n    \u003e\u003e\u003e sprite = make_sprite(video)\n\n\nStoring assets\n---------------\n\n.. code:: pycon\n\n    \u003e\u003e\u003e from madam.core import InMemoryStorage\n    \u003e\u003e\u003e storage = InMemoryStorage()\n    \u003e\u003e\u003e storage['hero'] = (asset, {'homepage', 'featured'})\n    \u003e\u003e\u003e retrieved_asset, tags = storage['hero']\n    \u003e\u003e\u003e # Filter by metadata\n    \u003e\u003e\u003e jpegs = list(storage.filter(mime_type='image/jpeg'))\n    \u003e\u003e\u003e # Filter by tags\n    \u003e\u003e\u003e featured = list(storage.filter_by_tags({'featured'}))\n","funding_links":[],"categories":["Python","hacktoberfest"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feseifert%2Fmadam","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feseifert%2Fmadam","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feseifert%2Fmadam/lists"}