Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mosaic plugin #2352

Closed
wants to merge 631 commits into from
Closed

Mosaic plugin #2352

wants to merge 631 commits into from

Conversation

SusannaMaria
Copy link
Contributor

@SusannaMaria SusannaMaria commented Dec 28, 2016

It's really fun to learn python within beets. :-)
Some months ago I wrote a simple java-application which created a mosaic of my cover art from beet lib. I decided yesterday to implement a very first plugin which covers this feature.

Here is the result fetching my whole lib, took ~1 minute. I resized the result with irfanview because it is 50 MB.

mosaic

I will continue on the develop add documentation and meaningful configuration ... and code-documentation blush
Right now it's only tested on windows10 with python2.7

@SusannaMaria SusannaMaria changed the title Mosaicplugin Mosaic plugin Dec 28, 2016
Copy link
Member

@nathdwek nathdwek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job! Welcome to beets!

You should probably set your editor a bit to work with python in general and the beets codebase in particular.
We follow pep8 relatively closely. At a first glance, you should at least have:

  • Autoconvert tabs to 4 spaces
  • Autoremove trailing whitespace
  • Max line length = 80

If possible, the best (and easiest!) option would be to use a pep8 linter, from CLI or IDE.

covers = list();

for album in albums:
self._log.info(u'#{}#', album.artpath)
Copy link
Member

@nathdwek nathdwek Dec 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here album.artpath is displayed before checking if it is not None. Is this a concern?

if not os.path.exists(album.artpath):
continue

covers.append(album.artpath)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this could be more elegantly written as

if album.artpath and os.path.exists(album.artpath):
    #cover list and logging
else:
    #is some form of logging needed here?

cmd = ui.Subcommand('mosaic', help=u"create mosaic from coverart")

def func(lib, opts, args):
self._generate_montage(lib, lib.albums(ui.decargs(args)), u'mos.png')
Copy link
Member

@nathdwek nathdwek Dec 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the mosaic filename be chosen by the user, at least optionally? It makes sense from a ux point of view.
Also, this would make the code more sensible, as for now _generate_montage has the output_fn parameter which is always hardcoded to u'mos.png'. That made me scratch my head a little bit.

embedart for example could be an inspiration for this. Moreover, embedart also does not let the user choose the extension, similarly to this plugin, so you could use the exact same convention and ux.

@nathdwek
Copy link
Member

This looks awesome and very pretty! I'll give it a run on linux asap.

Looking forward to seeing the docs and finished ux! Do you think this plugin is fit for some testing? Is there some business logic to isolate?

Copy link
Member

@sampsyo sampsyo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow; this is awesome! I'm also excited to give this a try. ✨

One little style note: in standard PEP8 style, the recommendation is to use 4 spaces for indentation (as opposed to real "tab" characters).

@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
# This file is part of beets.
# Copyright 2015-2016, Ohm Patel.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to put your own name here. 😃



class MosaicCoverArtPlugin(BeetsPlugin):
col_size = 4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like col_size might be unused?


def _generate_montage(self, lib, albums, output_fn):
fullwidth=0;
fullheight=0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python doesn't need semicolons. 🦆

@nathdwek
Copy link
Member

nathdwek commented Dec 29, 2016

A mosaic

It took a bit of bulldozing, but still, congrats!! 🥇

My observations:

  • It does not find the cover file for several albums which in fact do have a cover art. Maybe my library isn't as well janitored as I think it is, but still it seems weird to fail on albums which have a cover.jpg and embedded art.
  • I notice that sometimes there are gaps in the mosaic, as if for some albums nothing is pasted on the mosaic.

Some more minor comments:

  • What to do if the number of album is prime?
  • How to handle albums which are not square?
  • Could it randomize the order?

To solve those last two creatively, I'd suggest (just throwing that out there, maybe I could it myself if I really want it) to play with the overlap. This would give a more "natural mosaic" look while allowing to cheat on numerous constraints!

@bearcatsandor
Copy link

Couldn't one use this with bpd or the play command to create wallpapers and statuses that change as you change things?

@nathdwek
Copy link
Member

BTW the code with the minimal changes for a working plugin, which I used to do the little tests is at nathdwek/beets@d5f1d5b . @SusannaMaria you could maybe pull from there to get going?

@SusannaMaria
Copy link
Contributor Author

Thanks guys for the reply. I really want to follow the pep rules, and for sure I want to consider the issues related to ux.

@SusannaMaria
Copy link
Contributor Author

@nathdwek

  1. prime: My Idea was to have a squre mosaic and if it's not fiting because it is a prime just add a new row containing the tail of root calculation.
  2. Right now I have no creative solution for this, in the beginning I would just crop to squre
  3. The idea is to use the order which comes out of the query, do we have already a feature to randomize the query output?

@nathdwek
Copy link
Member

I think the two first points are perfectly reasonable, my idea would be sort of a bonus if anything.
As for the third point, we don't have exactly that yet, but it is absolutely the right approach! Shame on me for thinking about implementing this at the plugin level 😞 .

@SusannaMaria
Copy link
Contributor Author

SusannaMaria commented Dec 29, 2016

Some ideas regarding config, inspired by http://www.imagemagick.org/Usage/montage/#montage
mosaic:
geometry: 100x100+3+3
tile: square # or e.g. x3 3x
label: $album
background: ffffff
result_filename: mosaic.png
show_mosaic: yes
watermark_filename: tool.jpg
watermark_alpha: 0.4

@SusannaMaria
Copy link
Contributor Author

I added a blending of a picture to the whole mosaic
mos

in this example I used
tool

Maybe it's interesting to have something like a watermark in it. Maybe the github-user icon.

@mried
Copy link
Contributor

mried commented Dec 29, 2016

I've implemented the same thing (beside a lot of other things) some time ago in my arttools plugin with the artcollage command. You can find it here: https://github.com/mried/beetsplug. But don't expect to much: There's no watermark and has only a very simple configuration (output filename and tile size). I'm using it to create such a mosaic for every directory containing subdirectories. So I've got a mosaic for the whole library, for each genre and artist.

@sampsyo
Copy link
Member

sampsyo commented Dec 29, 2016

Very cool! It looks like this is passing our style checker now too. Woohoo!

I think all this needs is some plugin documentation before it's mergeable.

@SusannaMaria
Copy link
Contributor Author

I have a bug with processing the command-option. Right now you can not pass option to the beet mosaic command but the query.
Added a config, which can be used, but not the command -options :-(

mosaic:
mosaic: mos.png
watermark: c:/temp/tool.png
watermark_alpha: 0.5

I will work on the documentation maybe you can give me some hint, what I'm doing wrong in the
def func(lib, opts, args) function

@sampsyo
Copy link
Member

sampsyo commented Dec 29, 2016

Cool! There's actually an easy way to handle command-line arguments that override defaults from the configuration file. Here are the relevant docs:
http://confuse.readthedocs.io/en/latest/#command-line-options

Calling something like self.config.set_args(opts) will overlay the configuration with values from the command line. Then you can just access the configuration as normal, with no conditional, to get the merged values.

@nathdwek
Copy link
Member

Is it possible that there are some miscalculation of the mosaic size? It seems that my bigger mosaics are systematically missing what seems like a last row?

@SusannaMaria
Copy link
Contributor Author

Added a creation of dummy cover if the artpath is not given. I need to add a font therefor I moved the plugin into a subfolder and changed the manifest.in.

Guess I need some days more to check for bugs and documentation...

@sampsyo
Copy link
Member

sampsyo commented Dec 30, 2016

Hmm… including assets authored by other people can cause weird licensing headaches, especially for the people who package beets for OS package managers. Maybe we can make the plugin automatically download FreeSans when it first needs it? (Or use an installed font if one is available?)

@SusannaMaria
Copy link
Contributor Author

@sampsyo yes I understood, i have commented the font code out. Because I want to have a feature to label the cover however it's better to plan a solution which cause no issues. I will start with documentation and code comments tomorrow. Hope I get the bug with command-line fixed.

@SusannaMaria
Copy link
Contributor Author

That a weirdo bug.

   from PIL import Image

cause to fail all checks.

@sampsyo
Copy link
Member

sampsyo commented Dec 30, 2016

OK, sounds good!

That's weird about the test failure. I don't see your plugin is getting imported during the tests (since there are no tests yet). If we do want to add tests, we'll of course have to instruct the test harness to install PIL/Pillow. But that shouldn't be a problem yet… I'll have to look a little closer. Sorry about that!

@sampsyo
Copy link
Member

sampsyo commented Dec 31, 2016

Hmm… I'm not sure when this changed, but Travis has stopped complaining about the missing dependency! It does have some style suggestions, though.

Copy link
Member

@sampsyo sampsyo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great so far! Here are a few small suggestions along the way.


from PIL import Image
import math
from parse import *
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's usually best to avoid "star imports," which make it difficult to tell where names come from.

'geometry'].get(str)
else:
geometry = opts.background
albums = lib.albums(ui.decargs(args))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you've already used self.config.set_args, you don't need all these if/else blocks for fallbacks. For example, this suffices just fine:

geometry = self.config['geometry'].get(str)

In fact, this is probably what you want:

geometry = self.config['geometry'].as_str()

No need to test whether the value is present; just use it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, you don't even need to pass around stuff like geometry as a parameter—the code that needs it can just use self.config['geometry'].as_str() right there.

To use the ``mosaic`` plugin, first enable it in your configuration (see
:ref:`using-plugins`). Then, install the `Pillow`_ library by typing::

pip install Pillow
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this plugin also uses a library called parse, so you probably want to mention that here. (Or just use regular expressions instead.)

@SusannaMaria
Copy link
Contributor Author

Maybe you can give me some advise because of unit testing? Right now I have only one test implemented. which just generates a 2x2 mosaic with size 359x359. I compare the image size at the end.

Ideas

  1. Load different Image types, should I store examples in the rsrc folder?
  2. Robustness, different "crazy" options like negative geometry? (Have to implement a check for this)
  3. A very big mosaic with 10.000 pieces?

@SusannaMaria
Copy link
Contributor Author

https://tympanus.net/Tutorials/ThumbnailGridExpandingPreview/
Thinking about going html5...as Extension of Web 🤔

@sampsyo
Copy link
Member

sampsyo commented Jul 17, 2017

Very cool! I actually think a small test is probably fine in this case. If problems come up with strange parameters like very large numbers of albums, we can add tests for those too, but just getting the basic functionality covered is a great place to be for a new plugin.

The web thing is a neat idea too! Especially if there were still a way to flatten to a standalone image…

Signed-off-by: Susanna Maria Hepp <susanna.hepp@de.bosch.com>
@SusannaMaria
Copy link
Contributor Author

Just added a second usecase and faced a problem with png-Watermark.
Coverage is at 88%
beetsplug/mosaic/init.py 130 15 88%
@sampsyo thank you again for your great support and motivation. I will now refactor (again) and kick out the own directory in plugins. I'm using now a mechanism to download a ttf from google-font, will make it configurable.

Any change to put this plugin into master after QA and cleaning with static mosaic creation, or should I continue merging into the web-frontend as HTML5 with canvas?

@sampsyo
Copy link
Member

sampsyo commented Jul 18, 2017

Very cool! Yes, I'd be happy to merge this into master whenever you think it's ready. Maybe we can hit the green button after your next round of refactoring?

@SusannaMaria
Copy link
Contributor Author

added option to download and use font:
beet mosaic --geometry=150x150+1+1 -a 0.5 -w D:\temp\alpha.jpg -f https://github.com/google/fonts/raw/master/ofl/actor/Actor-Regular.ttf
or in config:
mosaic:
font: https://github.com/google/fonts/raw/master/ofl/aladin/Aladin-Regular.ttf

@sampsyo
Copy link
Member

sampsyo commented Jul 18, 2017

Nice. Would you mind mentioning the new font option in the documentation so people know how to use it?

Also, I’m not 100% sure what’s going on, but the GitHub pull request view is for some reason showing a bunch of unrelated changed. Maybe it’s a strange consequence of rebasing? I could dig a little deeper to find out what’s wrong, but maybe it’s clearer from your side.

@SusannaMaria
Copy link
Contributor Author

Because of the long break of 6 months I merged the master into the feature branch before I continued 3 days ago. Sorry because of the mess.

@sampsyo
Copy link
Member

sampsyo commented Jul 19, 2017

Ah, no problem. It makes the diff display look weird, but I think it will still merge correctly on the command line.

@SusannaMaria
Copy link
Contributor Author

SusannaMaria commented Jul 19, 2017

Okay that's it from my side for the mosaic plugin for now. Unfortunately I'm unable to understand the Problem with https://travis-ci.org/beetbox/beets/jobs/255377806
If I have fixed this problem, I really like to continue on the similarity plugin now. @sampsyo, if you accept my pull-request that would be great.

Because of similarity: guess it's best to use a conservative approach to store relations with Flexible Field Types. Lets see, but more or less I want to explore the best way to visualize the graph. Okay offtoic for this pull request :-)

@sampsyo
Copy link
Member

sampsyo commented Jul 20, 2017

Wow; that Travis failure is indeed really weird. I suspect it may be unrelated to your new plugin… I’ll look into it a bit more deeply. Thanks for all your effort on this! ✨

@SusannaMaria
Copy link
Contributor Author

Maybe its the download of the font. I never asked the question, where I have to store the font permanently?

        filename = fonturl[fonturl.rfind("/") + 1:]
        fontpath = os.path.join(os.path.dirname(__file__), filename)

so its stored at the same location of the plugin. Feeling guilty ...

@SusannaMaria
Copy link
Contributor Author

SusannaMaria commented Nov 1, 2018

"The deceased were taken from the grave" ...
Come back to my music-lib and worked a little on tagging and cleaning up my music which I have bought since 2017. So I went back to the mosaic plugin which never made it.

I never was satisfied with the static generation of one file, but I have found pyqtgraph today, very neat lib.

grafik

I really don't know if exploring 3000 albums by coverart makes really sense. But maybe within 99 years I come up with some implementation which brings value.

@SusannaMaria SusannaMaria deleted the Mosaicplugin branch June 22, 2019 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.