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

Example of handler creation and box implementation for MP4 Tika work #3

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Source/com/drew/imaging/mp4/Mp4Reader.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,14 @@ private static void processBoxes(StreamReader reader, long atomEnd, Mp4Handler<?
while (atomEnd == -1 || reader.getPosition() < atomEnd) {

Box box = new Box(reader);
if (box.type.equals("meta")) {
// TODO: Figure out how to support containers that are also FullBox types (extra 4 bytes for version and flag)
System.out.println("THIS NEEDS TO BE CHANGED.");
reader.skip(4);
}
Copy link
Owner Author

Choose a reason for hiding this comment

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

I am not sure the best way to do this.

We consider "meta" boxes to be a container, which they are. However, they are also technically full box implementations, so they have an extra 4 bytes. This is a hacky way to bypass this information for now and continue reading into the container.


// Determine if fourCC is container/atom and process accordingly.
// Unknown atoms will be skipped

if (handler.shouldAcceptContainer(box)) {
processBoxes(reader, box.size + reader.getPosition() - 8, handler.processContainer(box, context), context);
} else if (handler.shouldAcceptBox(box)) {
Expand Down
6 changes: 2 additions & 4 deletions Source/com/drew/metadata/mp4/Mp4BoxHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ public boolean shouldAcceptBox(@NotNull Box box)
|| box.type.equals(Mp4BoxTypes.BOX_HANDLER)
|| box.type.equals(Mp4BoxTypes.BOX_MEDIA_HEADER)
|| box.type.equals(Mp4BoxTypes.BOX_TRACK_HEADER)
|| box.type.equals(Mp4BoxTypes.BOX_USER_DATA)
|| box.type.equals(Mp4BoxTypes.BOX_USER_DEFINED);
}

Expand All @@ -68,7 +67,8 @@ public boolean shouldAcceptContainer(@NotNull Box box)
return box.type.equals(Mp4ContainerTypes.BOX_TRACK)
|| box.type.equals(Mp4ContainerTypes.BOX_METADATA)
|| box.type.equals(Mp4ContainerTypes.BOX_MOVIE)
|| box.type.equals(Mp4ContainerTypes.BOX_MEDIA);
|| box.type.equals(Mp4ContainerTypes.BOX_MEDIA)
|| box.type.equals(Mp4ContainerTypes.BOX_USER_DATA);
Copy link
Owner Author

Choose a reason for hiding this comment

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

Officially add "udta" as a container type.

}

@Override
Expand All @@ -90,8 +90,6 @@ public Mp4Handler<?> processBox(@NotNull Box box, @Nullable byte[] payload, Mp4C
} else if (box.type.equals(Mp4BoxTypes.BOX_USER_DEFINED)) {
Mp4UuidBoxHandler userBoxHandler = new Mp4UuidBoxHandler(metadata);
userBoxHandler.processBox(box, payload, context);
} else if (box.type.equals(Mp4BoxTypes.BOX_USER_DATA)) {
processUserData(box, reader, payload.length);
Copy link
Owner Author

Choose a reason for hiding this comment

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

"udta" is a container. We can move this underlying logic to the ItemListBox (where it belongs).

}
} else {
if (box.type.equals(Mp4ContainerTypes.BOX_COMPRESSED_MOVIE)) {
Expand Down
6 changes: 5 additions & 1 deletion Source/com/drew/metadata/mp4/Mp4BoxTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
package com.drew.metadata.mp4;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
* @author Payton Garland
Expand All @@ -39,7 +42,7 @@ public class Mp4BoxTypes
public static final String BOX_MEDIA_HEADER = "mdhd";
public static final String BOX_TRACK_HEADER = "tkhd";
public static final String BOX_USER_DEFINED = "uuid";
public static final String BOX_USER_DATA = "udta";
public static final String BOX_ITEM_LIST = "ilst";
Copy link
Owner Author

Choose a reason for hiding this comment

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

Tika MP4 changes essentially implements the "ilst" box.


private static final ArrayList<String> _boxList = new ArrayList<String>();

Expand All @@ -56,5 +59,6 @@ public class Mp4BoxTypes
_boxList.add(BOX_MEDIA_HEADER);
_boxList.add(BOX_TRACK_HEADER);
_boxList.add(BOX_USER_DEFINED);
_boxList.add(BOX_ITEM_LIST);
}
}
5 changes: 4 additions & 1 deletion Source/com/drew/metadata/mp4/Mp4HandlerFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public class Mp4HandlerFactory
private static final String HANDLER_VIDEO_MEDIA = "vide";
private static final String HANDLER_HINT_MEDIA = "hint";
private static final String HANDLER_TEXT_MEDIA = "text";
private static final String HANDLER_META_MEDIA = "meta";
private static final String HANDLER_NRT_META_MEDIA = "meta";
private static final String HANDLER_META_MEDIA = "mdir";
Copy link
Owner Author

Choose a reason for hiding this comment

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

The "ilst" box lies under the "mdir" (metadata) handler.

Documented under "QuickTime Handler Keys" https://exiftool.org/TagNames/QuickTime.html


private Mp4Handler<?> caller;

Expand All @@ -51,6 +52,8 @@ public Mp4Handler<?> getHandler(HandlerBox box, Metadata metadata, Mp4Context co
return new Mp4HintHandler(metadata, context);
} else if (type.equals(HANDLER_TEXT_MEDIA)) {
return new Mp4TextHandler(metadata, context);
} else if (type.equals(HANDLER_NRT_META_MEDIA)) {
return new Mp4NRTMetaHandler(metadata, context);
Copy link
Owner Author

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 this is the best naming of the old "meta" handler. This is what ExifTool calls it [NRT Metadata], but I think we should verify if this naming is proper.

https://exiftool.org/TagNames/QuickTime.html

} else if (type.equals(HANDLER_META_MEDIA)) {
return new Mp4MetaHandler(metadata, context);
}
Expand Down
145 changes: 145 additions & 0 deletions Source/com/drew/metadata/mp4/boxes/ItemListBox.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright 2002-2019 Drew Noakes and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* More information about this project is available at:
*
* https://drewnoakes.com/code/exif/
* https://github.com/drewnoakes/metadata-extractor
*/
package com.drew.metadata.mp4.boxes;

import com.drew.lang.SequentialReader;
import com.drew.metadata.mp4.media.Mp4MetaBoxDirectory;

import java.io.EOFException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;


public class ItemListBox extends Box
{
private class ItemListEntry
Copy link
Owner Author

Choose a reason for hiding this comment

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

Hacky key/value class for our itemMap such that we can map the item list key to the directory key (and actual value).

{
private int tag;
private String val;

ItemListEntry(int tag) {
this.tag = tag;
this.val = null;
}

int getTag() {
return this.tag;
}

void setVal(String val) {
this.val = val;
}

String getVal() {
return this.val;
}
}

public static Map<String, ItemListEntry> itemMap;

{
itemMap = new HashMap<String, ItemListEntry>();

itemMap.put("�alb", new ItemListEntry(Mp4MetaBoxDirectory.TAG_ALBUM));
itemMap.put("aART", new ItemListEntry(Mp4MetaBoxDirectory.TAG_ALBUM_ARTIST));
Copy link
Owner Author

Choose a reason for hiding this comment

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

Maps the item list keys to their corresponding directory key (and value if set later)

}

public ItemListBox(SequentialReader reader, Box box) throws IOException
{
super(box);

long totalRead = 0;
try {
while (totalRead < box.size) {
long recordLen = reader.getUInt32();
String fieldName = reader.getString(4);
long fieldLen = reader.getUInt32();
String typeName = reader.getString(4);//data
totalRead += 16;
if ("data".equals(typeName)) {
reader.skip(8);//not sure what these are
totalRead += 8;
int toRead = (int) fieldLen - 16;
if (toRead <= 0) {
//log?
return;
}
if ("covr".equals(fieldName)) {
//covr can be an image file, e.g. png or jpeg
//skip this for now
reader.skip(toRead);
} else if ("cpil".equals(fieldName)) {
int compilationId = (int) reader.getByte();
// metadata.set(XMPDM.COMPILATION, compilationId);
} else if ("trkn".equals(fieldName)) {
if (toRead == 8) {
long numA = reader.getUInt32();
long numB = reader.getUInt32();
// metadata.set(XMPDM.TRACK_NUMBER, (int)numA);
} else {
//log
reader.skip(toRead);
}
} else if ("disk".equals(fieldName)) {
int a = reader.getInt32();
short b = reader.getInt16();
// metadata.set(XMPDM.DISC_NUMBER, a);
} else {
String val = reader.getString(toRead);
if (itemMap.containsKey(fieldName)) {
ItemListEntry entry = itemMap.get(fieldName);
entry.setVal(val);
itemMap.put(fieldName, entry);
Copy link
Owner Author

Choose a reason for hiding this comment

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

I didn't really touch the "ilst" processing part except for right here. We add everything to the item map if it is known.

}
// try {
// addMetadata(fieldName, val);
// } catch (SAXException e) {
// //need to punch through IOException catching in MP4Reader
// throw new RuntimeSAXException(e);
// }
}

totalRead += toRead;
} else {
int toSkip = (int) recordLen - 16;
if (toSkip <= 0) {
//log?
return;
}
reader.skip(toSkip);
totalRead += toSkip;
}
}
} catch (EOFException e) {
System.out.println("EOF reached");
Copy link
Owner Author

Choose a reason for hiding this comment

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

The current implementation needs to be modified to avoid EOF.

Apple documents how to properly parse this atom, but upon first glance, it's a bit involved https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/Metadata/Metadata.html

I don't mind leaving this catch here for now.

}
}

public void addMetadata(Mp4MetaBoxDirectory directory)
{
for (Map.Entry<String, ItemListEntry> entry : itemMap.entrySet()) {
if (entry.getValue().getVal() != null) {
directory.setString(entry.getValue().getTag(), entry.getValue().getVal());
Copy link
Owner Author

Choose a reason for hiding this comment

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

Here, we iterate over the entire itemMap that we have been populating.

If the value isn't null, then we add it to the directory.

There are a couple of issues as is:

  1. Some values will require additional processing. This will likely just entail some more conditionals.
  2. Not all values will be strings. Up to author on implementation of this.

}
}
}
}
1 change: 1 addition & 0 deletions Source/com/drew/metadata/mp4/boxes/UserDataBox.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class UserDataBox extends Box {
public UserDataBox(@NotNull final SequentialReader reader, @NotNull final Box box, int length) throws IOException {
super(box);

// TODO: This logic should be added to ItemListBox by adding a new item for "©xyz"
Copy link
Owner Author

Choose a reason for hiding this comment

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

This is the original location support that Drew added a while back. Since we are implementing the ItemListbox, it can actually be moved to the new ItemListBox file.

while (reader.getPosition() < length) {
long size = reader.getUInt32();
if (size <= 4)
Expand Down
31 changes: 31 additions & 0 deletions Source/com/drew/metadata/mp4/media/Mp4MetaBoxDescriptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2002-2019 Drew Noakes and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* More information about this project is available at:
*
* https://drewnoakes.com/code/exif/
* https://github.com/drewnoakes/metadata-extractor
*/
package com.drew.metadata.mp4.media;

import com.drew.metadata.TagDescriptor;

public class Mp4MetaBoxDescriptor extends TagDescriptor<Mp4MetaBoxDirectory>
{
public Mp4MetaBoxDescriptor(Mp4MetaBoxDirectory directory)
{
super(directory);
}
}
60 changes: 60 additions & 0 deletions Source/com/drew/metadata/mp4/media/Mp4MetaBoxDirectory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2002-2019 Drew Noakes and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* More information about this project is available at:
*
* https://drewnoakes.com/code/exif/
* https://github.com/drewnoakes/metadata-extractor
*/
package com.drew.metadata.mp4.media;

import com.drew.lang.annotations.NotNull;

import java.util.HashMap;

public class Mp4MetaBoxDirectory extends Mp4MediaDirectory
{
public static final Integer TAG_ALBUM = 1001;
public static final Integer TAG_ALBUM_ARTIST = 1002;
Copy link
Owner Author

Choose a reason for hiding this comment

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

Add all of the ItemList keys that are known here.


@NotNull
private static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>();

static
{
Mp4MetaBoxDirectory.addMp4MediaTags(_tagNameMap);
_tagNameMap.put(TAG_ALBUM, "Album");
_tagNameMap.put(TAG_ALBUM_ARTIST, "Album Artist");
}

public Mp4MetaBoxDirectory()
{
this.setDescriptor(new Mp4MetaBoxDescriptor(this));
}

@NotNull
@Override
public String getName()
{
return "QuickTime Metadata";
}

@NotNull
@Override
protected HashMap<Integer, String> getTagNameMap()
{
return _tagNameMap;
}
}
Loading