Skip to content

Commit

Permalink
Implement Popularimeter beetbox#23
Browse files Browse the repository at this point in the history
  • Loading branch information
Ingo Fischer committed Jan 6, 2020
1 parent 75ad2c6 commit 2956c4d
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 58 deletions.
57 changes: 5 additions & 52 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,57 +1,10 @@
dist: trusty
language: python
sudo: false

env:
global:
# Undocumented feature of nose-show-skipped.
NOSE_SHOW_SKIPPED: 1
services:
- docker

matrix:
include:
- python: 2.7.13
env: {TOX_ENV: py27-cov, COVERAGE: 1}
- python: 2.7.13
env: {TOX_ENV: py27-test}
- python: 3.4
env: {TOX_ENV: py34-test}
- python: 3.5
env: {TOX_ENV: py35-test}
- python: 3.6
env: {TOX_ENV: py36-test}
- python: 3.7
env: {TOX_ENV: py37-test}
dist: xenial
- python: 3.8-dev
env: {TOX_ENV: py38-test}
dist: xenial
- python: pypy
env: {TOX_ENV: pypy-test}
- python: 2.7
env: {TOX_ENV: py27-flake8}
- python: 3.5
env: {TOX_ENV: py35-flake8}

# Non-Python dependencies.
addons:
apt:
packages:
- bash-completion

# To install dependencies, tell tox to do everything but actually running the
# test.
install:
- travis_retry pip install 'tox<=3.8.1'
- travis_retry tox -e $TOX_ENV --notest

script:
- tox -e $TOX_ENV

# Report coverage to codecov.io.
before_install:
- "[ ! -z $COVERAGE ] && travis_retry pip install codecov || true"
after_success:
- "[ ! -z $COVERAGE ] && codecov || true"
- make build

cache:
pip: true
script:
- make tox
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ RUN apt-get update \
&& apt-get clean

COPY requirements.txt .
COPY requirements-dev.txt .
RUN pip install -r requirements.txt
RUN pip install -r requirements-dev.txt

COPY . .
RUN pip install .
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,27 @@ tox:
test:
$(DOCKER_CMD) python -m unittest discover .

.PHONY: testpopm
testpopm:
$(DOCKER_CMD) python -m unittest test.test_mediafile.MP3Test

.PHONY: lint
lint:
$(DOCKER_CMD) flake8

.PHONY: ipython
ipython:
$(DOCKER_CMD) ipython

.PHONY: virtualenv
virtualenv:
virtualenv --python=/usr/bin/python3.7 venv
. ./venv/bin/activate && \
pip install -r requirements.txt && \
pip install -r requirements-dev.txt && \
pip install .


.PHONY: testpy
testpy:
$(DOCKER_CMD) python _test.py
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
MediaFile: read and write audio files' tags in Python
=====================================================

.. image:: https://travis-ci.org/ifischer/mediafile.svg?branch=master
:target: https://travis-ci.org/ifischer/mediafile
.. image:: https://travis-ci.com/ifischer/mediafile.svg?branch=develop
:target: https://travis-ci.com/ifischer/mediafile

.. image:: http://img.shields.io/pypi/v/mediafile.svg
:target: https://pypi.python.org/pypi/mediafile
Expand Down
65 changes: 65 additions & 0 deletions _test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import os
from mediafile import *
from mutagen.id3 import ID3, POPM

# import hunter
# hunter.trace(action=hunter.CallPrinter)


def mutagen_inspect(filename):
os.system(f"mutagen-inspect {filename} | grep POP")


def clear_popm(filename):
id3 = ID3(filename)
id3.delall('POPM')
id3.save()


def clear_priv(filename):
id3 = ID3(filename)
id3.delall('PRIV')
id3.save()


def set_popm_mutagen(filename, email, rating=None, count=None):
id3 = ID3(filename)
id3['POPM'] = POPM(email=email, rating=rating, count=count)
id3.save()


def set_popm_mediafile(filename, email, rating, count):
mf = MediaFile(filename)
mf.popm = POPM(email=email, rating=rating, count=count)
mf.save()


def get_popm_mutagen(filename):
id3 = ID3(filename)
return id3.getall('POPM')


def get_popm_mediafile(filename):
mf = MediaFile(filename)
return mf.popm


if __name__ == '__main__':
filename = 'test/rsrc/popm.mp3'
clear_popm(filename)
clear_priv(filename)

set_popm_mutagen(filename, email='[email protected]', rating=5)
print(get_popm_mutagen(filename))

clear_popm(filename)

# set_popm_mutagen(filename, email='[email protected]', count=5)
# print(get_popm_mutagen(filename))

# set_popm_mediafile(filename, email='[email protected]', rating=5, count=5)
# set_popm_mediafile(filename, email='[email protected]', rating=5, count=5)
# print("after: {}".format(get_popm_mediafile(filename)))
#
# mf = MediaFile(filename)
# from ipdb import set_trace; set_trace()
53 changes: 52 additions & 1 deletion mediafile.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import mutagen.asf

import codecs
import collections
import datetime
import re
import base64
Expand All @@ -56,7 +57,8 @@
import six


__all__ = ['UnreadableFileError', 'FileTypeError', 'MediaFile']
__all__ = ['UnreadableFileError', 'FileTypeError', 'MediaFile',
'Popularimeter']

log = logging.getLogger(__name__)

Expand All @@ -78,6 +80,10 @@

PREFERRED_IMAGE_EXTENSIONS = {'jpeg': 'jpg'}

Popularimeter = collections.namedtuple(
'Popularimeter', ['email', 'rating', 'count']
)


# Exceptions.

Expand Down Expand Up @@ -1222,6 +1228,8 @@ def _none_value(self):
return False
elif self.out_type == six.text_type:
return u''
elif self.out_type == dict:
return {}


class ListMediaField(MediaField):
Expand Down Expand Up @@ -1414,6 +1422,34 @@ def __delete__(self, mediafile):
delattr(mediafile, 'images')


class PopmMediaField(MediaField):
pass


class MP3PopmStorageStyle(StorageStyle):
formats = ['MP3']

def get(self, mutagen_file):
"""Returns POPM dictionary: {EMAIL: {'rating': RATING, 'count': COUNT}}
"""
return {
popm.email: {'rating': popm.rating, 'count': popm.count}
for popm in mutagen_file.tags.getall('POPM')
}

def set(self, mutagen_file, value):
"""Set POPM. 'count' will be set to 0 by default."""
popm_list = [
mutagen.id3.POPM(
email=email,
rating=value[email]['rating'],
count=value[email].get('count', 0)
)
for email in value.keys()
]
mutagen_file.tags.setall('POPM', popm_list)


class QNumberField(MediaField):
"""Access integer-represented Q number fields.
Expand Down Expand Up @@ -1849,6 +1885,10 @@ def update(self, dict):
StorageStyle('MUSICBRAINZ_ALBUMCOMMENT'),
ASFStorageStyle('MusicBrainz/Album Comment'),
)
popm = PopmMediaField(
MP3PopmStorageStyle(key='POPM', as_type=list),
out_type=dict
)

# Release date.
date = DateField(
Expand Down Expand Up @@ -2090,6 +2130,17 @@ def update(self, dict):
ASFStorageStyle('INITIALKEY'),
)

# @property
# def popm(self):
# # return {}
# return self._popm
#
# @popm.setter
# def popm(self, value):
# self._popm = value
# # from ipdb import set_trace; set_trace()
# # pass

@property
def length(self):
"""The duration of the audio in seconds (a float)."""
Expand Down
3 changes: 3 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
hunter==3.0.5
ipdb==0.12.3
ipython==7.10.1
5 changes: 5 additions & 0 deletions snippets.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
popm_tags = mediafile.mgfile.tags.getall('POPM')
return {
tag.email: Popularimeter(email=tag.email, rating=tag.rating, count=tag.count)
for tag in popm_tags
}
Binary file added test/rsrc/popm.mp3
Binary file not shown.
38 changes: 35 additions & 3 deletions test/test_mediafile.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@
import datetime
import time
import unittest

from six import assertCountEqual

from test import _common
from mediafile import MediaFile, Image, \
ImageType, CoverArtField, UnreadableFileError
from mediafile import MediaFile, Image, ImageType, CoverArtField, \
UnreadableFileError


class ArtTestMixin(object):
Expand Down Expand Up @@ -755,6 +756,37 @@ def test_unknown_apic_type(self):
mediafile = self._mediafile_fixture('image_unknown_type')
self.assertEqual(mediafile.images[0].type, ImageType.other)

def test_write_single_popm(self):
mediafile = self._mediafile_fixture('empty')
test_email = '[email protected]'
mediafile.popm = {
test_email: {'rating': 1, 'count': 1}
}
mediafile.save()
self.assertEqual(mediafile.popm, {
test_email: {'rating': 1, 'count': 1}
})

def test_write_popm_without_count(self):
mediafile = self._mediafile_fixture('empty')
test_email = '[email protected]'
mediafile.popm = {test_email: {'rating': 1}}
mediafile.save()
self.assertEqual(mediafile.popm, {
test_email: {'rating': 1, 'count': 0}
})

def test_write_multiple_popm(self):
mediafile = self._mediafile_fixture('full')
mediafile.popm = {'[email protected]': {'rating': 1, 'count': 1},
'[email protected]': {'rating': 2, 'count': 2}}
mediafile.save()

self.assertEqual(mediafile.popm, {
'[email protected]': {'rating': 1, 'count': 1},
'[email protected]': {'rating': 2, 'count': 2},
})


class MP4Test(ReadWriteTestBase, PartialTestMixin,
ImageStructureTestMixin, unittest.TestCase):
Expand Down Expand Up @@ -975,7 +1007,7 @@ def test_properties_from_readable_fields(self):

def test_known_fields(self):
fields = list(ReadWriteTestBase.tag_fields)
fields.extend(('encoder', 'images', 'genres', 'albumtype'))
fields.extend(('encoder', 'images', 'genres', 'albumtype', 'popm'))
assertCountEqual(self, MediaFile.fields(), fields)

def test_fields_in_readable_fields(self):
Expand Down

0 comments on commit 2956c4d

Please sign in to comment.