-
Notifications
You must be signed in to change notification settings - Fork 28
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
File-like objects support #45
Conversation
724c135
to
bb72d66
Compare
Thanks for getting this started! If you're interested in changing |
It's connected as in case of FileThing it doesn't have to be |
That would be awesome, if you don't mind! |
Done! |
`MediaFile.as_dict() converts tags to dict usable in for example `MediaFile.update()` or just printing purposes.
Rebased on top of master. |
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.
A few miscellaneous comments within!
@@ -436,7 +436,6 @@ def test_write(self): | |||
shutil.copy(src, path) | |||
mf = mediafile.MediaFile(path) | |||
mf.read_only_field = "something terrible" | |||
mf.path = os.path.join(self.temp_dir, b'test.flac') |
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.
I don't quite see why this needs to be deleted.
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.
def test_write(self):
src = os.path.join(_common.RSRC, b'empty.flac')
path = os.path.join(self.temp_dir, b'test.flac')
shutil.copy(src, path)
mf = mediafile.MediaFile(path)
mf.read_only_field = "something terrible"
mf.path = os.path.join(self.temp_dir, b'test.flac')
mf.save()
self.assertNotIn(self.key, mf.mgfile.tags)
This is a whole function.
path
is already set to exactly this path and then handled to mediafile
in line:
mf = mediafile.MediaFile(path)
So it was setting mf.path
to mf.path
;D
Sooo instead of fixing this code when renaming .path
, I just removed it and it still works!
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.
Huh; interesting! I wonder why this was here in the first place.
@@ -1518,6 +1549,19 @@ def __init__(self, path, id3v23=False): | |||
# Set the ID3v2.3 flag only for MP3s. | |||
self.id3v23 = id3v23 and self.type == 'mp3' | |||
|
|||
@property | |||
def filesize(self): |
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.
Probably could use a docstring?
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.
It's pretty obvious to me, but if you say it's needed.. ;D
@@ -1468,20 +1495,24 @@ class MediaFile(object): | |||
"""Represents a multimedia file on disk and provides access to its | |||
metadata. | |||
""" | |||
def __init__(self, path, id3v23=False): | |||
@loadfile() | |||
def __init__(self, filething, id3v23=False): |
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.
Maybe the docstring needs updating to describe what a filething
is?
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.
I'm not sure if I can say what exactly is it without scaring users ;/
mutagen
itself just mentions that you can put string or filelike as first argument.
Problem is that technically this first argument is eaten by loadfile
decorator and it's not filething
from func definition. filething
from func definition is namedtuple
from mutagen._utils
.
So TLDR:
I'm not sure how to document this.
Do you have any idea?
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.
I think I'd just say that filething
can either be a string or a file-like object.
# Utility. | ||
|
||
|
||
def _update_filething(filething): |
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.
Also perhaps needs a docstring? Some explanation could help explain why this is necessary.
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.
The problem is I'm not exactly sure why and where exactly closing happens.
Debugging this issue was guessing game.
mutagen
was closing my files (only in case of local files, wtf) after reading and I couldn't find any .close()
call in whole mutagen code nor using debugger on own proxy file-like.
Anyway - this allows to reopen files in r+
when saving when opened as r
on loading.
Proxy file-like weren't closed at all..
I'm not sure what could I write in this docstring what's not written in comment anyway.
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.
I found the reason!
mutagen._util._openfile
closes files it opened just after function needing file finishes.
mediafile.py
Outdated
except UnreadableFileError: | ||
# reraise our errors without changes | ||
raise |
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.
Can you elaborate on why this is necessary now (when it wasn't before)?
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.
__init__
function is now whole wrapped by loadfile
decorator, which calls mutagen._util.loadfile
through mutagen_call
(original loadfile also raises exceptions) so we.. use whole __init__
function through mutagen_call
and then call it inside, so exceptions are caught by it two times.
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.
I'm now, after some time, considering removing mutagen_call
from there and catching this exceptions myself in decorator.
It probably would be cleaner solution...
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.
It's not cleaner solution as it finished almost as copy-pasta of mutagen_call
.
I'll just add one more comment here and leave it as it was in previous version.
except Exception as exc: | ||
# Isolate bugs in Mutagen. | ||
log.debug(u'%s', traceback.format_exc()) | ||
log.error(u'uncaught Mutagen exception in %s: %s', action, exc) | ||
raise MutagenError(path, exc) | ||
|
||
|
||
def loadfile(method=True, writable=False, create=False): |
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.
One last place where a docstring could be helpful… even just referencing the original loadfile
decorator could help explain why this exists.
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.
Ok ;D
Should be pretty easy.
fixes support for filelike objects resolves beetbox#35 Incompatible changes: - renames `path` argument to `MediaFile` to `filename` (compatibility with mutagen) MediaFile can now be created using following scheme: ``` fileobj = io.BytesIO() MediaFile(filename="myfile.mp3") MediaFile(fileobj=myfileobj) ``` but old style ``` MediaFile("myfile.mp3") MediaFile(myfileobj) ``` will also work.
This doesn't have to be path as we are using FileThing to access file - it doesn't even have to be any local file anyway FileThing should have name inside, so we can use it as filename to provide user readable representation of this FileThing to user
I think I fixed most of issues you asked about ;D |
I re-added It should be compatible with most of existing software now, as far as someone don't set path in their code. I don't see a point of doing this anyway as afaik it didn't change anything - saving still worked on previous file. |
For backwards compatibility Also changes `MediaFile.filename` to `@property` - holding reference to string is pointless and it shouldn't be settable.
. |
Just returning to this PR after a long delay—is everything in order from your perspective, @JuniorJPDJ? Maybe we should add something to the documentation about this new capability so people know they can use it with open files? |
mediafile.py
Outdated
def __init__(self, filething, id3v23=False): | ||
"""Constructs a new `MediaFile` reflecting the provided file. | ||
May throw `UnreadableFileError`. | ||
First argument can be a string (file path) or filelike object. |
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.
There's something about it.
IMO it's enough.
IMO it's enough. @up is line describing it |
bump |
Thanks for your patience! This all looks great. I have updated some documentation and will merge now. ✨ |
and simple and useful
MediaFile.as_dict()
functionResolves #35