Closed NCrusher74 closed 3 years ago
Ugh. nevermind. I just saw what I was missing. All I have to do is this:
func tagWithHeader(version: Version) throws -> Data {
var framesData = Data()
for (_, frame) in self.frames {
frame.version = version // <- ugh this is so obvious how did I miss this?
framesData.append(frame.encode)
}
var tagData = self.version.versionBytes
tagData.append(self.defaultFlag)
tagData.append(framesData.count.uInt32.encodingSynchsafe().beData)
tagData.append(framesData)
return tagData
}
Question:
I'm reworking my date handling to (hopefully) give my parsing the best chance to come up with a valid date if the date isn't in ISO8601-compliant format. Would something like this work?
private var dateFormatters: [DateFormatter] {
var formatters = [DateFormatter]()
let formatter = DateFormatter()
formatter.dateStyle = .full
formatters.append(formatter)
formatter.dateStyle = .long
formatters.append(formatter)
formatter.dateStyle = .medium
formatters.append(formatter)
formatter.dateStyle = .short
formatters.append(formatter)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-ddTHH:mm"
formatters.append(formatter)
formatter.dateFormat = "yyyy-MM-ddTHH"
formatters.append(formatter)
formatter.dateFormat = "yyyy-MM-dd"
formatters.append(formatter)
formatter.dateFormat = "MM-dd-yyyy"
formatters.append(formatter)
formatter.dateFormat = "yyyy-MM"
formatters.append(formatter)
formatter.dateFormat = "yyyy"
formatters.append(formatter)
return formatters
}
// then when parsing
if let date = isoFormatter.date(from: self) {
return date
} else {
for format in dateFormatters {
if let date = format.date(from: self) {
return date
} else {
return nil
}
}; return nil
}
}
DateFormatter
is a class, so you are making all those changes on the same one, and the array just contains many references to the same thing. I’m also pretty sure the format overrides the style anyway. (The style is how it decides which format to ask the system for.)
You’d have to adjust it to something like this:
private var formats: [String] {
return ["yyyy-MM-ddTHH:mm", "yyyy-MM-ddTHH", /* ... */]
}
private let dateFormatters: [DateFormatter] = formats.map { format in
let formatter = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = format
return formatter
}
Ah okay, I hadn't been sure about that part. Good to know. Thank you!
Talking an issue through here.
Back when we first worked on this in the spring, there was an issue I noticed with the
DateFrame
where, if you read in a tag in one format, then tried to switch to another format, if the version of the input tag didn't support the frame you were trying to add, it would throw an error for trying to encode a frame to a version that didn't support that frame, even though the output version did actually support the frame.At the time, I couldn't figure out why this was happening or how to fix it, but since I've changed things up, the problem has become more apparent.
It looks like what's happening is that the identifier string remains the identifier string appropriate to the version for the tag that was initially read in, even if the output tag is a different version. In some cases, this means a 4-character identifier has changed between versions and if, say, you were converting a version 2.4 tag to a version 2.2 tag (not that I can see any reason why anyone would want to do that, but who knows, maybe someone uses an app that plays nicer with version 2.2 than version 2.4) you would end up with a situation where your frame in your output file is wonky because the identifier has four characters instead of three, and the entire frame header is ten bytes instead of six.
I had thought perhaps I would just do away with the ability to convert from one version of tag to another by getting rid of the
version
parameter in thewrite
function but even then, I'm still bumping up against issues with this. For example, myNoMeta
test file has a tag header that is version 2.3, even though it doesn't have any frames in the tag. Which means if I'm trying to write to test version 2.2 frames on that file, I encounter this problem.But...it's been a hard few weeks and I'm scattered-brained. A few weeks ago, a big windstorm brought down our neighbor's tree onto our roof and punched some holes in our house. Except for some repairs in the roof and a few cracks in our drywall, we thought we had gotten fairly lucky, but it turns out it actually drove a vent pipe from the roof down into our drainage pipes, breaking them quite badly, so for three weeks, we've had raw sewage flowing into the crawlspace under our house and now the cleanup and repair job is going to be several orders of magnitude more massive than we had been prepared for and we're not even sure our house should be considered habitable at the moment.
Add that to the fact that most of the last couple of weeks, the air hasn't been breathable in our part of the world, as well as the state of the world in general, and you can see why I'd be a bit distracted.
So. yeah. Every time I try to pick through the code, my eyes start to cross and I can't figure out what I need to do to fix this. Or actually, I can, but it seems like whenever I start to try to change the
version
working from the end of the process (i.e. theMp3File.write(tag:outputLocation:) function) at some point it comes into conflict with the
version` that was initialized upon reading the existuing tag from the input file. There needs to be a way to... reassign the version to the frames before they're encoded for output, so that the proper frame header data is used.My head is starting to spin again, so I will once again be using you as my rubber duck.
To catch you up on what I've been doing...
The major change I did with this version of the library is I structured it a little more like the MP4 tagger. Instead of a
FrameProtocol
, I made aFrame
class where I'm storing all the common-to-every-frame items, like identifier, size, flags, and content data.As with atoms in the MP4 tagger, all the specific frame types are now subclasses of the
Frame
class. I've made a few things that were version-dependent properties of theVersion
enum, as well, such as the four-characteridString
for each identifier:And instead of having
FrameKey
as its own enum that is 95% a straight-up duplicate of theKnownIdentifier
enum, instead theframeKey
is a string property of theFrameIdentifier/KnownIdentifier
enums:I'm not entirely sure about this change yet. I like having the
frameKey
as aString
because it makes it a little more portable but...idk. Either way, it doesn't really matter to what I'm trying to work out here, just a note in case you were wondering.Since
Frame
is now a class, I got rid of theFrame
enum, and instead put the task of assigning a particular frame to a particular frame subclass as a function of theFrameIdentifier
enum.So my order of operations, as it were, goes something like this:
Step 1: Initialize
Mp3File
with the audio file location and initialize adata
property for the file data.Step 2: Run the file data through the
Tag
initializer, which is pretty much the same as it was before, with one significant exception:version
is a stored property inTag
. I'm...drawing a blank right now on why that was necessary, but I think it had something to do with making it logic simpler in the accessors in cases where a particular frame needs different handling depending on the version. It seemed expedient at the time, and this may or may not be contributing to the lack of flexibility with switching versions. I don't think so, however, because I noticed an issue with version-switching months ago.So we parse out the version in the tag initializer, and use that as a parameter for my frame-header parsing function, which is one of those extensions I told you about.
(
Tag
initializer)Step 3: ( extension)
Step 4: (FrameIdentifier)
Step 6: Parse the specific frame. (I'll use the
DateFrame
as an example, since that's one frame where I know this version issue becomes apparent.)Step 7: Initialize the
Frame
class, which looks like this:Step 8: Call
Frame.encode
from the function inTag
where we assemble all the frames and append them to the new tag header data.Step 9: Call
tagWithHeader
from the file-builder function:Step 10: Use the freshly built file in the
write
function:So, as of
Frame.encode
, which is called from Step 8, the version being considered is still the version from the source tag, rather than the output tag. I can work around this by simply editing the version when working with thetag
instance (maybe that's why I decided to make that a stored property ofTag
?)outputs a properly formatted frame for version 2.2:
Except...no. Just tested that. It will properly output frames that are being altered or added, but existing, untouched frames are still passed through with the old version data.
So, alternatively, working backward from the final step, I'd be looking at something like this:
Step 10: (This is how it used to be, but it never worked properly because, while it changed the tag header data, it didn't change how the frame headers were encoded.)
Step 9: (Note to self: need to work logic in for handling situations where the file has no existing tag, i.e. we create one from scratch and insert it at the beginning. Right now, I believe a file without a tag will throw an error.)
Step 8:
Step 7:
However... I would also need to change
contentData
to a function, instead of a computed property, because it, too, would need to take into account the version. Some frames are handled differently internally, depending on version, not just in the header data.Right now,
contentData
is established using the version of the input tag, not the output.So it looks like what I need to do is recalculate all the frame header data that gets added in
Frame.encode
with the new version, and that includes encoding<FrameSubclass>.contentData
in the individual frame types.I think where I'm stumbling is actually the frame-building initializer (as opposed to the frame-parsing initializer).
That has a
version
parameter, so iftag.version
is edited, and then thetag.<accessor>
is used, and thetag.<accessor>
initializes the frame using the frame-building initializer when it'sset
, then the frame will get the new version.But any frame that isn't accessed using one of the accessors still has the version it was initialized with. So it's not just a matter of encoding. I have to actually RE-initialize all the frames with the new version. I think?
Okay, yeah. I think that's what I need to do. Change
tagWithHeader
to this:Upon testing, this APPEARS to work. The frames that are passed through without alteration are being changed to have the proper header data. It feels scary and iffy though. I'm not at all confident this is the right way to go. Maybe it's just the fact that I'm using the parsing initializer to re-initialize in the final encoding process?
Or maybe I just need an "attagirl" after the month I've had. Or a stiff scotch? That would be good too.