MediaInfoAccessor.java
/*
* The MIT License
* Copyright © 2024-2025 Andy Miles
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.amilesend.mediainfo.lib;
import com.amilesend.mediainfo.type.InfoType;
import com.amilesend.mediainfo.type.Status;
import com.amilesend.mediainfo.type.StreamType;
import com.google.common.annotations.VisibleForTesting;
import com.sun.jna.Pointer;
import com.sun.jna.WString;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import java.util.Objects;
/** The object used by java applications to interact with the libMediaInfo library. */
@Slf4j
public class MediaInfoAccessor implements AutoCloseable {
private final Object lock = new Object();
private MediaInfoLibrary mediaInfoLibrary;
@Getter(AccessLevel.PACKAGE)
@Setter(AccessLevel.PACKAGE)
@VisibleForTesting
private Pointer mediaInfoLibPointer;
/**
* Creates a new {@code MediaInfo} object.
*
* @param mediaInfoLibrary the library instance
* @see MediaInfoLibrary
*/
public MediaInfoAccessor(@NonNull final MediaInfoLibrary mediaInfoLibrary) {
this.mediaInfoLibrary = mediaInfoLibrary;
mediaInfoLibPointer = newPointer();
}
/** Closes the library instance. */
@Override
public void close() {
dispose();
}
/** Disposes of the library instance reference. */
public void dispose() {
if (Objects.isNull(mediaInfoLibPointer)) {
return;
}
synchronized (lock) {
mediaInfoLibrary.close(mediaInfoLibPointer);
mediaInfoLibrary.deleteHandle(mediaInfoLibPointer);
mediaInfoLibPointer = null;
}
}
/**
* Opens a file to parse.
*
* @param fileName the full path and filename to open
* @return {@code true} if file was opened; else, {@code false}
*/
public boolean open(final String fileName) {
Validate.notBlank(fileName, "fileName must not be blank");
synchronized (lock) {
if (Objects.isNull(mediaInfoLibPointer)) {
mediaInfoLibPointer = newPointer();
}
int response = mediaInfoLibrary.open(mediaInfoLibPointer, new WString(fileName));
return response == Status.Accepted.getValue();
}
}
/**
* Prepares a memory buffer for reading and parsing media information from a stream.
*
* @param length the length of the buffer
* @param offset the byte offset to start reading from
* @return {@code true} if the buffer was successfully initialized; else, {@code false}
*/
public boolean openBufferInit(final long length, final long offset) {
Validate.isTrue(length > 0L, "length must be > 0");
Validate.isTrue(offset >= 0L, "offset must be >= 0");
if (Objects.isNull(mediaInfoLibPointer)) {
throw new IllegalStateException("MediaInfoLib Pointer is null. This happens when close()/dispose() has " +
"been invoked prior to invoking openBufferInit()");
}
synchronized (lock) {
int response = mediaInfoLibrary.openBufferInit(mediaInfoLibPointer, length, offset);
return response == Status.Accepted.getValue();
}
}
/**
* Reads from a memory buffer to parse media information and tags.
*
* @param buffer the buffer reference
* @param size the amount of data to read
* @return a bitfield with the following bits:
* <ul>
* <li>{@code 0} - Accepted (format is known)</li>
* <li>{@code 1} - Filled (data collected)</li>
* <li>{@code 2} - Buffer updated (further data required)</li>
* <li>{@code 3} - Buffer finalized (no further data required)</li>
* <li>{@code 4-15} - Reserved</li>
* <li>{@code 16-31} - User defined</li>
* </ul>
*/
public int openBufferContinue(@NonNull final byte[] buffer, final int size) {
Validate.isTrue(size > 0, "size must be > 0");
if (Objects.isNull(mediaInfoLibPointer)) {
throw new IllegalStateException("MediaInfoLib Pointer is null. This happens when close()/dispose() has " +
"been invoked prior to invoking openBufferContinue()");
}
synchronized (lock) {
return mediaInfoLibrary.openBufferContinue(mediaInfoLibPointer, buffer, size);
}
}
/**
* Tests if there is request to seek to another position in the stream.
*
* @return {@code -1} if there is no more data to seek to; else, the seek position
*/
public long openBufferContinueGotoGet() {
if (Objects.isNull(mediaInfoLibPointer)) {
throw new IllegalStateException("MediaInfoLib Pointer is null. This happens when close()/dispose() has " +
"been invoked prior to invoking openBufferContinueGotoGet()");
}
synchronized (lock) {
return mediaInfoLibrary.openBufferContinueGotoGet(mediaInfoLibPointer);
}
}
/**
* Closes the buffer upon read completion.
*
* @return {@code 0} if the buffer has been read or null; else non-0 value if an error occurred.
*/
public int openBufferFinalize() {
if (Objects.isNull(mediaInfoLibPointer)) {
throw new IllegalStateException("MediaInfoLib Pointer is null. This happens when close()/dispose() has " +
"been invoked prior to invoking openBufferFinalize()");
}
synchronized (lock) {
return mediaInfoLibrary.openBufferFinalize(mediaInfoLibPointer);
}
}
/** Closes a file handle that was previously opened. */
public void closeHandle() {
if (Objects.isNull(mediaInfoLibPointer)) {
throw new IllegalStateException("MediaInfoLib Pointer is null. This happens when close()/dispose() has " +
"been invoked prior to invoking closeHandle()");
}
synchronized (lock) {
mediaInfoLibrary.close(mediaInfoLibPointer);
}
}
/**
* Get all details about a file.
*
* @return All details about a file in one string
*/
public String inform() {
if (Objects.isNull(mediaInfoLibPointer)) {
throw new IllegalStateException("MediaInfoLib Pointer is null. This happens when close()/dispose() has " +
"been invoked prior to invoking inform()");
}
synchronized (lock) {
return mediaInfoLibrary.inform(mediaInfoLibPointer, 0).toString();
}
}
/**
* Get a piece of information about a file (parameter is a string).
*
* @param streamType the stream type
* @param streamNumber the stream number
* @param parameter the parameter you are looking for in the stream (e.g., resolution, codec, bitrate, etc.)
* @return the query result, or empty if there is a problem or not found
* @see StreamType
*/
public String get(final StreamType streamType, final int streamNumber, final String parameter) {
return get(streamType, streamNumber, parameter, InfoType.Text, InfoType.Name);
}
/**
* Get a piece of information about a file (parameter is a string).
*
* @param streamType the stream type
* @param streamNumber the stream number
* @param parameter the parameter you are looking for in the stream (e.g., resolution, codec, bitrate, etc.)
* @param infoType the type of information about the parameter
* @see StreamType
* @see InfoType
*/
public String get(
final StreamType streamType,
final int streamNumber,
final String parameter,
final InfoType infoType) {
return get(streamType, streamNumber, parameter, infoType, InfoType.Name);
}
/**
* Get a piece of information about a file (parameter is a string). For a list of available parameters, please
* refer to <a href="https://github.com/MediaArea/MediaInfoLib/blob/master/Source/Resource/Text/Stream/General.csv">
* General.csv</a>.
*
* @param streamType the stream type
* @param streamNumber the stream number
* @param parameter the parameter you are looking for in the stream (e.g., resolution, codec, bitrate, etc.)
* @param infoType the type of information about the parameter
* @param searchType describes where to look for the parameter
* @return the queries information; or an empty string if there was a problem
*/
public String get(
@NonNull final StreamType streamType,
final int streamNumber,
final String parameter,
@NonNull final InfoType infoType,
@NonNull final InfoType searchType) {
Validate.isTrue(streamNumber >= 0, "streamNumber must be >= 0");
Validate.notBlank(parameter, "parameter must not be blank");
if (Objects.isNull(mediaInfoLibPointer)) {
throw new IllegalStateException("MediaInfoLib Pointer is null. This happens when close()/dispose() has " +
"been invoked prior to invoking get()");
}
synchronized (lock) {
return mediaInfoLibrary.get(
mediaInfoLibPointer,
streamType.ordinal(),
streamNumber,
new WString(parameter),
infoType.ordinal(),
searchType.ordinal()).toString();
}
}
/**
* Get a piece of information about a file (parameter is an integer that represents the parameter index).
*
* @param streamType the stream type
* @param streamNumber the stream number
* @param parameterIndex the parameter index that you are looking for in the stream
* @return a string about information you search, an empty string if there is a problem
*/
public String get(final StreamType streamType, final int streamNumber, final int parameterIndex) {
return get(streamType, streamNumber, parameterIndex, InfoType.Text);
}
/**
* Get a piece of information about a file (parameter is an integer that represents the parameter index).
*
* @param streamType the stream type
* @param streamNumber the stream number
* @param parameterIndex the parameter index that you are looking for in the stream
* @param infoType the type of information you want about the parameter (the text, the measure, the help...)
* @return the information or an empty string if there is a problem
*/
public String get(
@NonNull final StreamType streamType,
final int streamNumber,
final int parameterIndex,
@NonNull final InfoType infoType) {
Validate.isTrue(streamNumber >= 0, "streamNumber must be > 0");
Validate.isTrue(parameterIndex >= 0, "parameterIndex must be >= 0");
if (Objects.isNull(mediaInfoLibPointer)) {
throw new IllegalStateException("MediaInfoLib Pointer is null. This happens when close()/dispose() has " +
"been invoked prior to invoking get()");
}
synchronized (lock) {
return mediaInfoLibrary.getI(
mediaInfoLibPointer,
streamType.ordinal(),
streamNumber,
parameterIndex,
infoType.ordinal()).toString();
}
}
/**
* Gets the number of streams for the given stream type.
*
* @param streamType the stream type
* @return number of streams of the given stream type
*/
public int getStreamCount(@NonNull final StreamType streamType) {
final String streamCount = get(streamType, 0, "StreamCount");
if (StringUtils.isEmpty(streamCount)) {
return 0;
}
return Integer.parseInt(streamCount);
}
/**
* Gets the number of streams for the given stream type or the total count of information parameters for a stream.
*
* @param streamType the stream type
* @param streamNumber the stream number
* @return number of streams of the given stream type
*/
public int getStreamOrParameterCount(@NonNull final StreamType streamType, final int streamNumber) {
Validate.isTrue(streamNumber >= 0, "streamNumber must be > 0");
synchronized (lock) {
return mediaInfoLibrary.countGet(mediaInfoLibPointer, streamType.ordinal(), streamNumber);
}
}
/**
* Gets information about MediaInfo.
*
* @param option The name of option
* @return the option value
*/
public String getOption(final String option) {
return setOption(option, StringUtils.EMPTY);
}
/**
* Configures information about MediaInfo.
*
* @param option The name of option
* @param value The value of option
* @return {@code ""} means no; else, any other value means yes
*/
public String setOption(final String option, @NonNull final String value) {
Validate.notBlank(option, "option must not be blank");
synchronized (lock) {
return mediaInfoLibrary.option(mediaInfoLibPointer, new WString(option), new WString(value)).toString();
}
}
private Pointer newPointer() {
try {
return mediaInfoLibrary.newHandle();
} catch(final LinkageError error) {
throw new MediaInfoException(error);
}
}
}