-
Notifications
You must be signed in to change notification settings - Fork 14
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
Internal metadata keys #325
base: master
Are you sure you want to change the base?
Conversation
That was fast, awesome, thanks for the PR! I really like using I still feel like an additional class for the internal keys could make sense, especially if it inherited from
If it were in it's own class, the class could also lazy-initialize the
We would then override
This is getting rather long, but I will try to add my thoughts on some of the other open issues:
|
Thank you for your comments and suggestions. I will look into it and see what I can do. But be patient, I am really busy the new two weeks an I am unsure when I can look into this. |
Awesome, thank you, and as always: no hurry! 😊 |
I have split the There is still lots of cleanup etc. to do. But before I do that I would be grateful if you could have a glimpse at the code and comment on the general structure. Just to make sure that I do not have to do the same work twice when we decide to rearrange some larger things... 😊 |
So you did end up adding another class which combines the two handlers. Personally, I like this a lot, we keep the fetching and combining logic separated. Only downside is the need to more or less redefine the functions for I will add a few inline comments to clarify some of my ideas 😊 |
Yeah, as you initially suggested, three classes allow to split these different modules in a clean manor. |
Oh, sorry, the wording "expose" was probably rather bad. I fully agree that no-one should have to bother where metadata comes from. What I meant was to make the In this case I actually wonder if it also makes sense to make |
Ah, this makes much more sense. I thought you are talking about exposing it to the API 🙃 |
c60e20c
to
56775ac
Compare
Commit 71ac242 has somehow broken the How should we unit test this module? If I am not mistaken the metadata functionality is only tested via the metadata widget. Probably it would make sense to test these handlerclasses separately. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking great! I left a few comments that should hopefully fix the tests as well as make the complete CI pass. Have a nice week-end! 😊
Concerning the unit-tests: I agree that we should probably test them all in a nice and isolated fashion in If you like, you can take a shot at this and ask any questions. Otherwise I am happy to do this after merging. |
Thank you for your suggestions, hits and comments. I will go though it in the following week(s) and also give it a try to create some appropriate unit test. Though be warned, I have to not that before 😄 |
Awesome work, this is taking getting in good shape! 😊 I noticed that the
Finally, you somehow managed to wreck havoc with the git history 😆 I guess a simple rebase against the current master here should fix this. |
16ec200
to
800943d
Compare
733b23d
to
d41e384
Compare
Would you like some more time to work on this or is it time to take another look and hopefully provide some help? 😊 |
I am sorry that I have neglected this PR a little in the last few weeks It would actually be great if you could give me some hints any tips. I am kind off stuck with two things:
After solving the first two points, a follow up question is the following. Let's say I have defined the test I hope explaining me how to write these tests does not take more time if you would just write them yourself. |
No worries at all, I have neglected work on vimiv completely over the past months ... This took some debugging, and it indeed required writing the code, so I will just dump the snippets I came up with and some hopefully useful information for future PRs 😊 @pytest.fixture()
def dummy_image(qapp, tmp_path):
filename = str(tmp_path / "image.jpg") # We use pytest's tmp_path fixture to create the image in a tmp directory that is cleaned for us
QPixmap(300, 300).save(filename) # Let's use PyQt directly instead of another external module, requires the "qapp" fixture to create a default QApplication
return filename
@pytest.fixture
def metadata_handler(add_metadata_information, dummy_image, metadata_content):
assert pyexiv2 is not None, "pyexiv2 required to add metadata information"
add_metadata_information(dummy_image, metadata_content)
return MetadataHandler(dummy_image)
@pytest.fixture
def metadata_handler_piexif(dummy_image, metadata_handler):
# This is certainly not beautiful, but I could not come up with a consistent solution as we need pyexiv2
# to add the metadata information and the external handler is defined upon import level
metadata_handler._ext_handler = metadata._ExternalKeyHandlerPiexif(dummy_image)
return metadata_handler
def test_metadatahandler_fetch_key(metadata_handler, metadata_content):
for key, value in metadata_content.items():
fetched_key, _, fetched_value = metadata_handler.fetch_key(key)
assert fetched_key == key
try:
assert fetched_value == value.human_value
except AttributeError:
assert fetched_value == value.raw_value
def test_metadatahandler_fetch_key_piexif(metadata_handler_piexif, metadata_content):
# I also ended up writing an additional test for piexif as
# 1) the keys are in their short form and thus must be compared differently
# 2) there is no support for the human value also making this comparison different
for key, value in metadata_content.items():
fetched_key, _, fetched_value = metadata_handler_piexif.fetch_key(key)
short_key = key.rpartition(".")[-1]
assert fetched_key == short_key
assert fetched_value == value.raw_value and I had to modify the for tag in content.values():
_metadata[tag.key] = tag.value It seems like pyexiv2 invalidates the python import pyexiv2
from PyQt5.QtGui import QImage
tag = pyexiv2.ExifTag("Exif.Image.Copyright", "vimiv-AUTHORS-2021")
path = "image.jpg"
image = QImage(300, 300, QImage.Format_ARGB32)
image.save(path)
handler = pyexiv2.ImageMetadata(path)
handler.read()
# Uncomment for segfault
# handler[tag.key] = tag
handler[tag.key] = tag.value
handler.write()
handler = pyexiv2.ImageMetadata(path)
handler.read()
print(tag.human_value) Concerning the piexif part: Let me know if there are any questions and happy hacking 😊 |
aad33d0
to
d2e7f4b
Compare
Thanks @TornaxO7 for using vimiv and reporting this incidence! It is a bit unfortunate that vifm reports this an error but it is actually by purpose. You are missing the optional dependency which is required for metadata support. See here for the link to the required library: https://karlch.github.io/vimiv-qt/documentation/exif.html @karlch Maybe we should improve this log by indicating how to enable metadata support to prevent further confusion. Don't you think so too? |
Welcome to vimiv and thanks from me as well @TornaxO7. Yes, I fully agree with everything you said @jcjgraf! Something simple like _logger.warning(
"There is no exif support and therefore:\n"
"1. Exif data is lost when writing images to disk.\n"
"2. The `:metadata` command and associated `i` keybinding is not available.\n"
"3. The {exif-date-time} statusbar module is not available.\n\n"
"Please install pyexiv2 or piexif to silence this warning.\n"
"For more information see\n"
"https://karlch.github.io/vimiv-qt/documentation/exif.html.\n"
) could suffice IMHO. If you agree with this and the wording, I can just commit this snippet to master. |
- Propagate KeyError from KeyHandler - Do not catch UnsupportedMetadataOperation in metadatawidget since it will never be thrown - Fix lint
0cad693
to
aae1fe2
Compare
However, this is not supported by piexif
Again, that is not supported by piexif
f5c03ba
to
0c7e946
Compare
Upon loading metadata information of a image with a type not supported by `piexif` a not caught exception was raised. Thanks @BachoSeven for pointing this out!
60664d7
to
4475faa
Compare
This PR is finally ready for an in-depth review. I hope my commits are not too messy. I did not dear to clean it up more. I fear that I would break something 😄 I am not sure what happend with the CI pipeline. Somehow it has gone 😕 Probably due to the conflicts with the master. Commit 3050b22 is "cherry-pick" from #371 since it turned out to be a little bit too painfull to rebase this branch with master. Meaning, the conflicts with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again, this was a lot of great work!
The tests test what they should for the most part, but I feel like the structure could be improved in general. There are an awful lot of fixtures, maybe this could also be broken down with some more parametrization? If there is anything I can help with, just let me know!
None of this is super-critical, so feel free to tell me in case you don't want to go into the havoc of refactoring the tests, I can also take a look at this some other time. On the other hand, if you feel like it could be a fun way to play around with pytest, just go ahead and hack away 😊
value: {val}\ | ||
tag: {tag}" | ||
) | ||
if keyname != key: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is quite a brutal approach to look through every tag for every key. I propose keeping this for now, but considering thinking about an improvement, e.g. reading the piexif stuff into a dictionary we can deal with upon construction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Um yeah, I agree that this could be solved more efficiently. I have added it to the todo 😊
@@ -0,0 +1,335 @@ | |||
# vim: ft=python fileencoding=utf-8 sw=4 et sts=4 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I like having the fixtures at the top of a test module, followed by all the tests, to give some more structure to the file. Feel free to adopt this if you like 😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thought was to add more specific fixtures (ones which are not widely used) above the test cases in which they are used. But obviously I will change it to conform to the general styling rule of this project.
|
||
def test_external_keyhandler_get_date_time( | ||
external_keyhandler, external_keyhandler_piexif, dummy_image | ||
): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a really funky way to do this as pytest provides parametrization precisely for this purpose 😊
As an example, the (unused?) fixture at the top called external_handler
will make tests run twice, once with metadata.ExternalKeyHandler
as value for external_handler
and once with metadata._ExternalKeyHandlerPiexif
as the argument. This fixture could easily be modified to actually create the handler with some image as path, but it might be nice to do this within this test, so the structure would be something along:
- add metadata to dummy image if any
- create handler with path to dummy image
- compare to expected result
Both the metadata and the expected results could also be parameters, now not of the fixture but of the test itself. The structure would end up looking somewhat like this (dummy code):
@pytest.mark.parametrize("metadata, expected_date", [({"name": ExifTag("name", "value"}, "value"), ({}, "")]
def test_external_keyhandler_copy_metadata(external_handler, dummy_image, add_metadata_information, metadata, expected_date):
src = dummy_image("image.jpg")
add_metadata_information(src, metadata)
handler = external_handler(src)
assert handler.get_date_time() == expected_date
While this doesn't look much better than the four assert statements in your implementation, it does have a few advantages:
- Each assert is run in its own test, i.e. we test pyexiv2 and piexif as well as content / no content separately
- We can very easily add more combinations
This kind of logic applies to some more parts of the tests you wrote, e.g. the copy_metadata test could then also be parametrized instead of having a for loop. Hope this makes sense 😊
Mention the new internal key and improve the comparison between the two libraries
ca01071
to
a0bf5ba
Compare
Thanks @karlch for pointing them out
Thanks for catching some silly typos and mistakes and for the constructive feedback on my tests. I have written so many fixtures in order that I have great flexibility in how to combine them 🙃 But you are right, I have not read the full documentation of pytest yet, else I would probably not have written a single testcases by now 😏 I will do some more research on parametrisation and try to improve the test accordingly! |
@karlch Am I heading into the right direction based on the changes 2fb9a4c? What do you think about the fixtures |
This is a nice first step, I guess this style can also be used for the I am perfectly fine keeping those fixtures, you could also just define a global constant for the fixed dictionaries at the top to reduce the number of fixtures passed to a testcase. I.e. for example:
Independent of the fixtures, the |
2fb9a4c
to
b1ad894
Compare
b1ad894
to
f655bd3
Compare
This PR adds internal metadata keys to vimiv, as outlined in #324. Unlike @karlch has suggested, I have put everything into the preexisting
ExifHandler
. I had the feeling that the proposed idea of having separate classes for the internal and external keys is slightly overkill for the few internal keys we are actually adding.The basic idea is the following: Clients do not longer call the ExifHandlers child-specific
get_formatted_exif
butget_formatted_metadata
. This method is not overwritten by any child. It iterates over alldesired_keys
and handles all internal keys directly. For the exif keys, the child-specific_fetch_external_key
is called which extracts the exif using the appropriate library.get_formatted_metadata
combines all the extracted data and returns it to the client.To get the image dimension I use the
QImageReader
class instead of accessing the pixmap somehow. This has the advantage that it is fully decoupled from the qt pixmap of vimiv. This reader class is actually meant for accessing image properties without rendering a fullblown pixmap.In my view, the new functionality could be added the
ExifHandler
in a rather clean manor and the complexity of the class is still reasonable.Nevertheless, this PR is just a proposition and in the current state merely a proof-of-concept. If you think that it is still better to split the functionality I will redo this PR.
If we use this PR as a basis, then there are the following open TODOs:
get_formatted_metadata
I still redefine them multiple times. Would something like a dict cleanup the mess?get_keys
?