diff --git a/ios/.gitignore b/ios/.gitignore index b5c9ce0e3..c41b0cfe1 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -2,3 +2,8 @@ /Makefile /config.status /configure.tmp +/.deps/ +/.libs/ +*.trs +/unittests +/test-driver diff --git a/ios/configure b/ios/configure index 9a0b2edce..a51437717 100755 --- a/ios/configure +++ b/ios/configure @@ -9331,20 +9331,20 @@ fi $as_echo "$lt_cv_ld_force_load" >&6; } case $host_os in rhapsody* | darwin1.[012]) - _lt_dar_allow_undefined='' ;; + _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; darwin1.*) - _lt_dar_allow_undefined='' ;; + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; darwin*) # darwin 5.x on # if running on 10.5 or later, the deployment target defaults # to the OS version, if on x86, and 10.4, the deployment # target defaults to 10.4. Don't you love it? case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in 10.0,*86*-darwin8*|10.0,*-darwin[91]*) - _lt_dar_allow_undefined='${wl}dynamic_lookup' ;; + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; 10.[012][,.]*) - _lt_dar_allow_undefined='' ;; + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; 10.*) - _lt_dar_allow_undefined='${wl}dynamic_lookup' ;; + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; esac ;; esac diff --git a/ios/src/Makefile.am b/ios/src/Makefile.am index f6c2ef150..182da53e7 100644 --- a/ios/src/Makefile.am +++ b/ios/src/Makefile.am @@ -10,21 +10,27 @@ libmobileffmpeg_la_SOURCES = \ fftools_ffmpeg_hw.c \ fftools_ffmpeg_opt.c \ fftools_ffmpeg_videotoolbox.c \ + MediaInformation.m \ + MediaInformationParser.m \ MobileFFmpeg.m \ MobileFFmpegConfig.m \ mobileffmpeg_exception.m \ - Statistics.m + Statistics.m \ + StreamInformation.m include_HEADERS = \ ArchDetect.h \ fftools_cmdutils.h \ fftools_ffmpeg.h \ LogDelegate.h \ + MediaInformation.h \ + MediaInformationParser.h \ MobileFFmpeg.h \ MobileFFmpegConfig.h \ mobileffmpeg_exception.h \ Statistics.h \ - StatisticsDelegate.h + StatisticsDelegate.h \ + StreamInformation.h libmobileffmpeg_la_CFLAGS = $(CFLAGS) libmobileffmpeg_la_OBJCFLAGS = $(CFLAGS) diff --git a/ios/src/Makefile.in b/ios/src/Makefile.in index 1801c163e..1e16410e9 100644 --- a/ios/src/Makefile.in +++ b/ios/src/Makefile.in @@ -139,10 +139,13 @@ am_libmobileffmpeg_la_OBJECTS = libmobileffmpeg_la-ArchDetect.lo \ libmobileffmpeg_la-fftools_ffmpeg_hw.lo \ libmobileffmpeg_la-fftools_ffmpeg_opt.lo \ libmobileffmpeg_la-fftools_ffmpeg_videotoolbox.lo \ + libmobileffmpeg_la-MediaInformation.lo \ + libmobileffmpeg_la-MediaInformationParser.lo \ libmobileffmpeg_la-MobileFFmpeg.lo \ libmobileffmpeg_la-MobileFFmpegConfig.lo \ libmobileffmpeg_la-mobileffmpeg_exception.lo \ - libmobileffmpeg_la-Statistics.lo + libmobileffmpeg_la-Statistics.lo \ + libmobileffmpeg_la-StreamInformation.lo libmobileffmpeg_la_OBJECTS = $(am_libmobileffmpeg_la_OBJECTS) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) @@ -168,9 +171,12 @@ DEFAULT_INCLUDES = -I.@am__isrc@ depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/libmobileffmpeg_la-ArchDetect.Plo \ + ./$(DEPDIR)/libmobileffmpeg_la-MediaInformation.Plo \ + ./$(DEPDIR)/libmobileffmpeg_la-MediaInformationParser.Plo \ ./$(DEPDIR)/libmobileffmpeg_la-MobileFFmpeg.Plo \ ./$(DEPDIR)/libmobileffmpeg_la-MobileFFmpegConfig.Plo \ ./$(DEPDIR)/libmobileffmpeg_la-Statistics.Plo \ + ./$(DEPDIR)/libmobileffmpeg_la-StreamInformation.Plo \ ./$(DEPDIR)/libmobileffmpeg_la-fftools_cmdutils.Plo \ ./$(DEPDIR)/libmobileffmpeg_la-fftools_ffmpeg.Plo \ ./$(DEPDIR)/libmobileffmpeg_la-fftools_ffmpeg_filter.Plo \ @@ -375,21 +381,27 @@ libmobileffmpeg_la_SOURCES = \ fftools_ffmpeg_hw.c \ fftools_ffmpeg_opt.c \ fftools_ffmpeg_videotoolbox.c \ + MediaInformation.m \ + MediaInformationParser.m \ MobileFFmpeg.m \ MobileFFmpegConfig.m \ mobileffmpeg_exception.m \ - Statistics.m + Statistics.m \ + StreamInformation.m include_HEADERS = \ ArchDetect.h \ fftools_cmdutils.h \ fftools_ffmpeg.h \ LogDelegate.h \ + MediaInformation.h \ + MediaInformationParser.h \ MobileFFmpeg.h \ MobileFFmpegConfig.h \ mobileffmpeg_exception.h \ Statistics.h \ - StatisticsDelegate.h + StatisticsDelegate.h \ + StreamInformation.h libmobileffmpeg_la_CFLAGS = $(CFLAGS) libmobileffmpeg_la_OBJCFLAGS = $(CFLAGS) @@ -474,9 +486,12 @@ distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmobileffmpeg_la-ArchDetect.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmobileffmpeg_la-MediaInformation.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmobileffmpeg_la-MediaInformationParser.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmobileffmpeg_la-MobileFFmpeg.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmobileffmpeg_la-MobileFFmpegConfig.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmobileffmpeg_la-Statistics.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmobileffmpeg_la-StreamInformation.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmobileffmpeg_la-fftools_cmdutils.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmobileffmpeg_la-fftools_ffmpeg.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmobileffmpeg_la-fftools_ffmpeg_filter.Plo@am__quote@ # am--include-marker @@ -588,6 +603,20 @@ libmobileffmpeg_la-ArchDetect.lo: ArchDetect.m @AMDEP_TRUE@@am__fastdepOBJC_FALSE@ DEPDIR=$(DEPDIR) $(OBJCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepOBJC_FALSE@ $(AM_V_OBJC@am__nodep@)$(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(OBJC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libmobileffmpeg_la_OBJCFLAGS) $(OBJCFLAGS) -c -o libmobileffmpeg_la-ArchDetect.lo `test -f 'ArchDetect.m' || echo '$(srcdir)/'`ArchDetect.m +libmobileffmpeg_la-MediaInformation.lo: MediaInformation.m +@am__fastdepOBJC_TRUE@ $(AM_V_OBJC)$(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(OBJC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libmobileffmpeg_la_OBJCFLAGS) $(OBJCFLAGS) -MT libmobileffmpeg_la-MediaInformation.lo -MD -MP -MF $(DEPDIR)/libmobileffmpeg_la-MediaInformation.Tpo -c -o libmobileffmpeg_la-MediaInformation.lo `test -f 'MediaInformation.m' || echo '$(srcdir)/'`MediaInformation.m +@am__fastdepOBJC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmobileffmpeg_la-MediaInformation.Tpo $(DEPDIR)/libmobileffmpeg_la-MediaInformation.Plo +@AMDEP_TRUE@@am__fastdepOBJC_FALSE@ $(AM_V_OBJC)source='MediaInformation.m' object='libmobileffmpeg_la-MediaInformation.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepOBJC_FALSE@ DEPDIR=$(DEPDIR) $(OBJCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepOBJC_FALSE@ $(AM_V_OBJC@am__nodep@)$(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(OBJC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libmobileffmpeg_la_OBJCFLAGS) $(OBJCFLAGS) -c -o libmobileffmpeg_la-MediaInformation.lo `test -f 'MediaInformation.m' || echo '$(srcdir)/'`MediaInformation.m + +libmobileffmpeg_la-MediaInformationParser.lo: MediaInformationParser.m +@am__fastdepOBJC_TRUE@ $(AM_V_OBJC)$(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(OBJC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libmobileffmpeg_la_OBJCFLAGS) $(OBJCFLAGS) -MT libmobileffmpeg_la-MediaInformationParser.lo -MD -MP -MF $(DEPDIR)/libmobileffmpeg_la-MediaInformationParser.Tpo -c -o libmobileffmpeg_la-MediaInformationParser.lo `test -f 'MediaInformationParser.m' || echo '$(srcdir)/'`MediaInformationParser.m +@am__fastdepOBJC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmobileffmpeg_la-MediaInformationParser.Tpo $(DEPDIR)/libmobileffmpeg_la-MediaInformationParser.Plo +@AMDEP_TRUE@@am__fastdepOBJC_FALSE@ $(AM_V_OBJC)source='MediaInformationParser.m' object='libmobileffmpeg_la-MediaInformationParser.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepOBJC_FALSE@ DEPDIR=$(DEPDIR) $(OBJCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepOBJC_FALSE@ $(AM_V_OBJC@am__nodep@)$(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(OBJC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libmobileffmpeg_la_OBJCFLAGS) $(OBJCFLAGS) -c -o libmobileffmpeg_la-MediaInformationParser.lo `test -f 'MediaInformationParser.m' || echo '$(srcdir)/'`MediaInformationParser.m + libmobileffmpeg_la-MobileFFmpeg.lo: MobileFFmpeg.m @am__fastdepOBJC_TRUE@ $(AM_V_OBJC)$(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(OBJC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libmobileffmpeg_la_OBJCFLAGS) $(OBJCFLAGS) -MT libmobileffmpeg_la-MobileFFmpeg.lo -MD -MP -MF $(DEPDIR)/libmobileffmpeg_la-MobileFFmpeg.Tpo -c -o libmobileffmpeg_la-MobileFFmpeg.lo `test -f 'MobileFFmpeg.m' || echo '$(srcdir)/'`MobileFFmpeg.m @am__fastdepOBJC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmobileffmpeg_la-MobileFFmpeg.Tpo $(DEPDIR)/libmobileffmpeg_la-MobileFFmpeg.Plo @@ -616,6 +645,13 @@ libmobileffmpeg_la-Statistics.lo: Statistics.m @AMDEP_TRUE@@am__fastdepOBJC_FALSE@ DEPDIR=$(DEPDIR) $(OBJCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepOBJC_FALSE@ $(AM_V_OBJC@am__nodep@)$(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(OBJC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libmobileffmpeg_la_OBJCFLAGS) $(OBJCFLAGS) -c -o libmobileffmpeg_la-Statistics.lo `test -f 'Statistics.m' || echo '$(srcdir)/'`Statistics.m +libmobileffmpeg_la-StreamInformation.lo: StreamInformation.m +@am__fastdepOBJC_TRUE@ $(AM_V_OBJC)$(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(OBJC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libmobileffmpeg_la_OBJCFLAGS) $(OBJCFLAGS) -MT libmobileffmpeg_la-StreamInformation.lo -MD -MP -MF $(DEPDIR)/libmobileffmpeg_la-StreamInformation.Tpo -c -o libmobileffmpeg_la-StreamInformation.lo `test -f 'StreamInformation.m' || echo '$(srcdir)/'`StreamInformation.m +@am__fastdepOBJC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmobileffmpeg_la-StreamInformation.Tpo $(DEPDIR)/libmobileffmpeg_la-StreamInformation.Plo +@AMDEP_TRUE@@am__fastdepOBJC_FALSE@ $(AM_V_OBJC)source='StreamInformation.m' object='libmobileffmpeg_la-StreamInformation.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepOBJC_FALSE@ DEPDIR=$(DEPDIR) $(OBJCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepOBJC_FALSE@ $(AM_V_OBJC@am__nodep@)$(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(OBJC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libmobileffmpeg_la_OBJCFLAGS) $(OBJCFLAGS) -c -o libmobileffmpeg_la-StreamInformation.lo `test -f 'StreamInformation.m' || echo '$(srcdir)/'`StreamInformation.m + mostlyclean-libtool: -rm -f *.lo @@ -772,9 +808,12 @@ clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ distclean: distclean-am -rm -f ./$(DEPDIR)/libmobileffmpeg_la-ArchDetect.Plo + -rm -f ./$(DEPDIR)/libmobileffmpeg_la-MediaInformation.Plo + -rm -f ./$(DEPDIR)/libmobileffmpeg_la-MediaInformationParser.Plo -rm -f ./$(DEPDIR)/libmobileffmpeg_la-MobileFFmpeg.Plo -rm -f ./$(DEPDIR)/libmobileffmpeg_la-MobileFFmpegConfig.Plo -rm -f ./$(DEPDIR)/libmobileffmpeg_la-Statistics.Plo + -rm -f ./$(DEPDIR)/libmobileffmpeg_la-StreamInformation.Plo -rm -f ./$(DEPDIR)/libmobileffmpeg_la-fftools_cmdutils.Plo -rm -f ./$(DEPDIR)/libmobileffmpeg_la-fftools_ffmpeg.Plo -rm -f ./$(DEPDIR)/libmobileffmpeg_la-fftools_ffmpeg_filter.Plo @@ -828,9 +867,12 @@ installcheck-am: maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/libmobileffmpeg_la-ArchDetect.Plo + -rm -f ./$(DEPDIR)/libmobileffmpeg_la-MediaInformation.Plo + -rm -f ./$(DEPDIR)/libmobileffmpeg_la-MediaInformationParser.Plo -rm -f ./$(DEPDIR)/libmobileffmpeg_la-MobileFFmpeg.Plo -rm -f ./$(DEPDIR)/libmobileffmpeg_la-MobileFFmpegConfig.Plo -rm -f ./$(DEPDIR)/libmobileffmpeg_la-Statistics.Plo + -rm -f ./$(DEPDIR)/libmobileffmpeg_la-StreamInformation.Plo -rm -f ./$(DEPDIR)/libmobileffmpeg_la-fftools_cmdutils.Plo -rm -f ./$(DEPDIR)/libmobileffmpeg_la-fftools_ffmpeg.Plo -rm -f ./$(DEPDIR)/libmobileffmpeg_la-fftools_ffmpeg_filter.Plo diff --git a/ios/src/MediaInformation.h b/ios/src/MediaInformation.h new file mode 100644 index 000000000..f9b1df7f5 --- /dev/null +++ b/ios/src/MediaInformation.h @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2018 Taner Sener + * + * This file is part of MobileFFmpeg. + * + * MobileFFmpeg is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MobileFFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MobileFFmpeg. If not, see . + */ + +#include +#include "StreamInformation.h" + +@interface MediaInformation : NSObject + +- (instancetype)init; + +/** + * Returns format. + * + * \return media format + */ +- (NSString*)getFormat; + +/** + * Sets media format. + * + * \param media format + */ +- (void)setFormat:(NSString*)format; + +/** + * Returns path. + * + * \return media path + */ +- (NSString*)getPath; + +/** + * Sets media path. + * + * \param media path + */ +- (void)setPath:(NSString*)path; + +/** + * Returns duration. + * + * \return media duration in milliseconds + */ +- (NSNumber*)getDuration; + +/** + * Sets media duration. + * + * \param media duration in milliseconds + */ +- (void)setDuration:(NSNumber*) duration; + +/** + * Returns start time. + * + * \return media start time in milliseconds + */ +- (NSNumber*)getStartTime; + +/** + * Sets media start time. + * + * \param media start time in milliseconds + */ +- (void)setStartTime:(NSNumber*)startTime; + +/** + * Returns bitrate. + * + * \return media bitrate in kb/s + */ +- (NSNumber*)getBitrate; + +/** + * Sets bitrate. + * + * \param media bitrate in kb/s + */ +- (void)setBitrate:(NSNumber*) bitrate; + +/** + * Returns unparsed media information. + * + * \return unparsed media information data + */ +- (NSString*)getRawInformation; + +/** + * Sets unparsed media information. + * + * \param unparsed media information data + */ +- (void)setRawInformation:(NSString*)rawInformation; + +/** + * Adds metadata. + * + * \param metadata key and value + */ +- (void)addMetadata:(NSString*)key :(NSString*)value; + +/** + * Returns all metadata entries. + * + * \return metadata dictionary + */ +- (NSDictionary*)getMetadataEntries; + +/** + * Adds new stream. + * + * \param new stream information + */ +- (void)addStream:(StreamInformation*) stream; + +/** + * Returns all streams + * + * \return streams array + */ +- (NSArray*)getStreams; + +@end diff --git a/ios/src/MediaInformation.m b/ios/src/MediaInformation.m new file mode 100644 index 000000000..0db2068e1 --- /dev/null +++ b/ios/src/MediaInformation.m @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2018 Taner Sener + * + * This file is part of MobileFFmpeg. + * + * MobileFFmpeg is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MobileFFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MobileFFmpeg. If not, see . + */ + +#include "MediaInformation.h" + +@implementation MediaInformation { + + /** + * Format + */ + NSString *format; + + /** + * Path + */ + NSString *path; + + /** + * Duration, in milliseconds + */ + NSNumber *duration; + + /** + * Start time, in milliseconds + */ + NSNumber *startTime; + + /** + * Bitrate, kb/s + */ + NSNumber *bitrate; + + /** + * Metadata map + */ + NSMutableDictionary *metadata; + + /** + * List of streams + */ + NSMutableArray *streams; + + /** + * Raw unparsed media information + */ + NSString *rawInformation; + +} + +- (instancetype)init { + self = [super init]; + if (self) { + format = nil; + path = nil; + duration = nil; + startTime = nil; + bitrate = nil; + metadata = [[NSMutableDictionary alloc] init]; + streams = [[NSMutableArray alloc] init]; + rawInformation = nil; + } + + return self; +} + +- (NSString*)getFormat { + return format; +} + +- (void)setFormat:(NSString*)newFormat { + format = newFormat; +} + +- (NSString*)getPath { + return path; +} + +- (void)setPath:(NSString*)newPath { + path = newPath; +} + +- (NSNumber*)getDuration { + return duration; +} + +- (void)setDuration:(NSNumber*)newDuration { + duration = newDuration; +} + +- (NSNumber*)getStartTime { + return startTime; +} + +- (void)setStartTime:(NSNumber*)newStartTime { + startTime = newStartTime; +} + +- (NSNumber*)getBitrate { + return bitrate; +} + +- (void)setBitrate:(NSNumber*)newBitrate { + bitrate = newBitrate; +} + +- (NSString*)getRawInformation { + return rawInformation; +} + +- (void)setRawInformation:(NSString*)newRawInformation { + rawInformation = newRawInformation; +} + +- (void)addMetadata:(NSString*)key :(NSString*)value { + metadata[key] = value; +} + +- (NSDictionary*)getMetadataEntries { + return metadata; +} + +- (void)addStream:(StreamInformation*)stream { + [streams addObject:stream]; +} + +- (NSArray*)getStreams { + return streams; +} + +@end diff --git a/ios/src/MediaInformationParser.h b/ios/src/MediaInformationParser.h new file mode 100644 index 000000000..591307c4c --- /dev/null +++ b/ios/src/MediaInformationParser.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Taner Sener + * + * This file is part of MobileFFmpeg. + * + * MobileFFmpeg is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MobileFFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MobileFFmpeg. If not, see . + */ + +#include +#include "MediaInformation.h" + +@interface MediaInformationParser : NSObject + +/** + * Extracts MediaInformation from given command output. + */ ++ (MediaInformation*)from: (NSString*)rawCommandOutput; + +/** + * Extracts StreamInformation from given input. + */ ++ (StreamInformation*)parseStreamBlock:(NSString*)input; + ++ (void (^)(NSString *__autoreleasing*, NSString *__autoreleasing*))parseInputBlock:(NSString*)input; ++ (void (^)(NSNumber *__autoreleasing*, NSNumber *__autoreleasing*, NSNumber *__autoreleasing*))parseDurationBlock:(NSString*)input; ++ (void (^)(NSString *__autoreleasing*, NSString *__autoreleasing*))parseMetadataBlock:(NSString*)input; ++ (void (^)(NSNumber *__autoreleasing*, NSNumber *__autoreleasing*))parseVideoDimensions: (NSString*)input; + ++ (NSString*)parseVideoStreamSampleAspectRatio: (NSString*)input; ++ (NSString*)parseVideoStreamDisplayAspectRatio: (NSString*)input; ++ (NSNumber*)parseAudioStreamSampleRate: (NSString*)input; ++ (NSString*)parseStreamType: (NSString*)input; ++ (NSString*)parseStreamCodec: (NSString*)input; ++ (NSString*)parseStreamFullCodec: (NSString*)input; ++ (NSNumber*)parseStreamIndex: (NSString*)input; ++ (NSNumber*)parseDuration: (NSString*)input; ++ (NSNumber*)parseStartTime: (NSString*)input; ++ (NSString*)substring:(NSString*)string from:(NSString*)start to:(NSString*)end ignoring:(NSArray*)ignoredTokens; ++ (NSString*)substring:(NSString*)string from:(NSString*)start ignoring:(NSArray*)ignoredTokens; ++ (NSString*)substring:(NSString*)string to:(NSString*)start ignoring:(NSArray*)ignoredTokens; ++ (int)index:(NSString*)string of:(NSString*)substring from:(int)startIndex times:(int)n; ++ (int)count:(NSString*)string of:(NSString*)substring; ++ (NSNumber*)toInteger: (NSString*)input; ++ (NSNumber*)toIntegerObject: (NSString*)input; ++ (NSString*)safeGet:(NSArray*)array from:(int)index; + +@end diff --git a/ios/src/MediaInformationParser.m b/ios/src/MediaInformationParser.m new file mode 100644 index 000000000..b042b99c6 --- /dev/null +++ b/ios/src/MediaInformationParser.m @@ -0,0 +1,590 @@ +/* + * Copyright (c) 2018 Taner Sener + * + * This file is part of MobileFFmpeg. + * + * MobileFFmpeg is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MobileFFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MobileFFmpeg. If not, see . + */ + +#include "MediaInformationParser.h" + +static NSDateFormatter* durationFormat; +static NSDate* referenceDuration; +static NSRegularExpression *parenthesesRegex; +static NSRegularExpression *bracketsRegex; + +@implementation MediaInformationParser + ++ (void)initialize { + NSError *localError = nil; + durationFormat = [[NSDateFormatter alloc] init]; + [durationFormat setDateFormat:@"HH:mm:ss"]; + + referenceDuration = [durationFormat dateFromString: @"00:00:00"]; + parenthesesRegex = [NSRegularExpression regularExpressionWithPattern:@"\\(.*\\)" options:NSRegularExpressionCaseInsensitive error:&localError]; + bracketsRegex = [NSRegularExpression regularExpressionWithPattern:@"\\[.*\\]" options:NSRegularExpressionCaseInsensitive error:&localError]; +} + ++ (MediaInformation*)from: (NSString*)rawCommandOutput { + MediaInformation* mediaInformation = [[MediaInformation alloc] init]; + + if (rawCommandOutput != nil) { + NSArray* split = [rawCommandOutput componentsSeparatedByString:@"\n"]; + Boolean metadata = false; + StreamInformation *lastCreatedStream = nil; + NSMutableString *rawInformation = [[NSMutableString alloc] init]; + + for (int i=0; i < [split count]; i++) { + NSString *outputLine = [split objectAtIndex:i]; + + if ([outputLine hasPrefix:@"["]) { + metadata = false; + continue; + } + + NSString *trimmedLine = [outputLine stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + + if ([trimmedLine hasPrefix:@"Input"]) { + metadata = false; + lastCreatedStream = nil; + + void(^blockPair)(NSString **, NSString **) = [MediaInformationParser parseInputBlock:trimmedLine]; + + NSString *format; + NSString *path; + blockPair(&format, &path); + + [mediaInformation setFormat:format]; + [mediaInformation setPath:path]; + } else if ([trimmedLine hasPrefix:@"Duration"]) { + metadata = false; + lastCreatedStream = nil; + + void(^blockTrio)(NSNumber **, NSNumber **, NSNumber **) = [MediaInformationParser parseDurationBlock:trimmedLine]; + + NSNumber *duration; + NSNumber *startTime; + NSNumber *bitrate; + + blockTrio(&duration, &startTime, &bitrate); + + [mediaInformation setDuration:duration]; + [mediaInformation setStartTime:startTime]; + [mediaInformation setBitrate:bitrate]; + } else if ([trimmedLine hasPrefix:@"Metadata"]) { + metadata = true; + } else if ([trimmedLine hasPrefix:@"Stream mapping"] || [trimmedLine hasPrefix:@"Press [q] to stop"] || [trimmedLine hasPrefix:@"Output"]) { + break; + } else if ([trimmedLine hasPrefix:@"Stream"]) { + metadata = false; + lastCreatedStream = [MediaInformationParser parseStreamBlock:trimmedLine]; + [mediaInformation addStream:lastCreatedStream]; + } else if (metadata) { + void(^blockPair)(NSString **, NSString **) = [MediaInformationParser parseMetadataBlock:trimmedLine]; + + NSString *key; + NSString *value; + blockPair(&key, &value); + + if (key != nil && value != nil) { + if (lastCreatedStream != nil) { + [lastCreatedStream addMetadata:key:value]; + } else { + [mediaInformation addMetadata:key:value]; + } + } + } + + [rawInformation appendString:outputLine]; + [rawInformation appendString:@"\n"]; + } + + [mediaInformation setRawInformation:rawInformation]; + } + + return mediaInformation; +} + ++ (void (^)(NSString *__autoreleasing*, NSString *__autoreleasing*))parseInputBlock:(NSString*)input { + NSString *format = [MediaInformationParser substring:input from:@"," to:@", from" ignoring:nil]; + NSString *path = [MediaInformationParser substring:input from:@"'" to:@"'" ignoring:nil]; + + return ^(NSString **s1, NSString **s2){ + *s1 = format; + *s2 = path; + }; +} + ++ (void (^)(NSNumber *__autoreleasing*, NSNumber *__autoreleasing*, NSNumber *__autoreleasing*))parseDurationBlock:(NSString*)input { + NSNumber *duration = [MediaInformationParser parseDuration:[MediaInformationParser substring:input from:@"Duration:" to:@"," ignoring:[[NSMutableArray alloc] initWithObjects:@"uration:", nil]]]; + NSNumber *start = [MediaInformationParser parseStartTime:[MediaInformationParser substring:input from:@"start:" to:@"," ignoring:[[NSMutableArray alloc] initWithObjects:@"tart:", nil]]]; + NSString *bitrateString = [MediaInformationParser substring:input from:@"bitrate:" ignoring:[[NSMutableArray alloc] initWithObjects:@"itrate:", @"kb/s", nil]]; + + NSNumber *bitrate = nil; + if (bitrateString != nil && ![bitrateString isEqualToString:@"N/A"]) { + bitrate = [MediaInformationParser toIntegerObject:bitrateString]; + } + + return ^(NSNumber **n1, NSNumber **n2, NSNumber **n3){ + *n1 = duration; + *n2 = start; + *n3 = bitrate; + }; +} + ++ (void (^)(NSString *__autoreleasing*, NSString *__autoreleasing*))parseMetadataBlock:(NSString*)input { + NSString *key = nil; + NSString *value = nil; + + if (input != nil) { + key = [MediaInformationParser substring:input to:@":" ignoring:[[NSMutableArray alloc] init]]; + value = [MediaInformationParser substring:input from:@":" ignoring:[[NSMutableArray alloc] init]]; + } + + return ^(NSString **s1, NSString **s2){ + *s1 = key; + *s2 = value; + }; +} + ++ (StreamInformation*)parseStreamBlock:(NSString*)input { + StreamInformation* streamInformation = [[StreamInformation alloc] init]; + + if (input != nil) { + [streamInformation setIndex:[MediaInformationParser parseStreamIndex:input]]; + + int typeBlockStartIndex = [MediaInformationParser index:input of:@":" from:0 times:2]; + if ((typeBlockStartIndex > -1) && (typeBlockStartIndex < [input length])) { + + NSString *streamBlock = [input substringFromIndex:(typeBlockStartIndex + 1)]; + + NSArray* parts = [streamBlock componentsSeparatedByString:@","]; + NSString *typePart = [MediaInformationParser safeGet:parts from:0]; + NSString *type = [MediaInformationParser parseStreamType:typePart]; + + [streamInformation setType:type]; + [streamInformation setCodec:[MediaInformationParser parseStreamCodec:typePart]]; + [streamInformation setFullCodec:[MediaInformationParser parseStreamFullCodec:typePart]]; + + NSString *part2 = [MediaInformationParser safeGet:parts from:1]; + NSString *part3 = [MediaInformationParser safeGet:parts from:2]; + NSString *part4 = [MediaInformationParser safeGet:parts from:3]; + NSString *part5 = [MediaInformationParser safeGet:parts from:4]; + + if ([@"video" isEqualToString:type]) { + int lastUsedPart = 1; + + if (part2 != nil) { + int pStart = [MediaInformationParser count:part2 of:@"("]; + int pEnd = [MediaInformationParser count:part2 of:@")"]; + + while (pStart != pEnd) { + lastUsedPart++; + NSString *newPart = [MediaInformationParser safeGet:parts from:lastUsedPart]; + if (newPart == nil) { + break; + } + part2 = [NSString stringWithFormat:@"%@,%@", part2, newPart]; + pStart = [MediaInformationParser count:part2 of:@"("]; + pEnd = [MediaInformationParser count:part2 of:@")"]; + } + + part2 = [part2 lowercaseString]; + part2 = [part2 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + + [streamInformation setFullFormat:part2]; + + NSString *formattedPart2 = [parenthesesRegex stringByReplacingMatchesInString:part2 options:0 range:NSMakeRange(0, [part2 length]) withTemplate:@""]; + formattedPart2 = [formattedPart2 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + [streamInformation setFormat:formattedPart2]; + } + + lastUsedPart++; + NSString *videoDimensionPart = [MediaInformationParser safeGet:parts from:lastUsedPart]; + if (videoDimensionPart != nil) { + NSString *videoLayout = [videoDimensionPart lowercaseString]; + videoLayout = [videoLayout stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + + void(^dimensions)(NSNumber **, NSNumber **) = [MediaInformationParser parseVideoDimensions:videoLayout]; + NSNumber *width; + NSNumber *height; + dimensions(&width, &height); + + [streamInformation setWidth:width]; + [streamInformation setHeight:height]; + + [streamInformation setSampleAspectRatio:[MediaInformationParser parseVideoStreamSampleAspectRatio:videoLayout]]; + [streamInformation setDisplayAspectRatio:[MediaInformationParser parseVideoStreamDisplayAspectRatio:videoLayout]]; + } + + for (int i = lastUsedPart + 1; i < [parts count]; i++) { + NSString *part = [parts objectAtIndex:i]; + + part = [parenthesesRegex stringByReplacingMatchesInString:part options:0 range:NSMakeRange(0, [part length]) withTemplate:@""]; + part = [part lowercaseString]; + + if ([part rangeOfString:@"kb/s"].location != NSNotFound) { + part = [part stringByReplacingOccurrencesOfString:@"kb/s" withString:@""]; + part = [part stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + NSNumber *bitrate = [MediaInformationParser toIntegerObject:part]; + [streamInformation setBitrate:bitrate]; + } else if ([part rangeOfString:@"fps"].location != NSNotFound) { + part = [part stringByReplacingOccurrencesOfString:@"fps" withString:@""]; + part = [part stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + [streamInformation setAverageFrameRate:part]; + } else if ([part rangeOfString:@"tbr"].location != NSNotFound) { + part = [part stringByReplacingOccurrencesOfString:@"tbr" withString:@""]; + part = [part stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + [streamInformation setRealFrameRate:part]; + } else if ([part rangeOfString:@"tbn"].location != NSNotFound) { + part = [part stringByReplacingOccurrencesOfString:@"tbn" withString:@""]; + part = [part stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + [streamInformation setTimeBase:part]; + } else if ([part rangeOfString:@"tbc"].location != NSNotFound) { + part = [part stringByReplacingOccurrencesOfString:@"tbc" withString:@""]; + part = [part stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + [streamInformation setCodecTimeBase:part]; + } + } + } + + if ([@"audio" isEqualToString:type]) { + if (part2 != nil) { + [streamInformation setSampleRate:[MediaInformationParser parseAudioStreamSampleRate:part2]]; + } + if (part3 != nil) { + NSString *formattedPart3 = [part3 lowercaseString]; + formattedPart3 = [formattedPart3 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + [streamInformation setChannelLayout:formattedPart3]; + } + if (part4 != nil) { + NSString *formattedPart4 = [part4 lowercaseString]; + formattedPart4 = [formattedPart4 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + [streamInformation setSampleFormat:formattedPart4]; + } + if (part5 != nil) { + NSString *formattedPart5 = [part5 lowercaseString]; + formattedPart5 = [parenthesesRegex stringByReplacingMatchesInString:formattedPart5 options:0 range:NSMakeRange(0, [formattedPart5 length]) withTemplate:@""]; + formattedPart5 = [formattedPart5 stringByReplacingOccurrencesOfString:@"kb/s" withString:@""]; + formattedPart5 = [formattedPart5 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + [streamInformation setBitrate:[MediaInformationParser toIntegerObject:formattedPart5]]; + } + } + } + } + + return streamInformation; +} + ++ (void (^)(NSNumber *__autoreleasing*, NSNumber *__autoreleasing*))parseVideoDimensions: (NSString*)input { + NSNumber *width = nil; + NSNumber *height = nil; + + if (input != nil) { + + NSString *formattedString = [input lowercaseString]; + formattedString = [bracketsRegex stringByReplacingMatchesInString:formattedString options:0 range:NSMakeRange(0, [formattedString length]) withTemplate:@""]; + + NSString *widthString = [MediaInformationParser substring:formattedString to:@"x" ignoring:[[NSMutableArray alloc] init]]; + NSString *heightString = [MediaInformationParser substring:formattedString from:@"x" ignoring:[[NSMutableArray alloc] init]]; + + width = [MediaInformationParser toIntegerObject:widthString]; + height = [MediaInformationParser toIntegerObject:heightString]; + } + + return ^(NSNumber **n1, NSNumber **n2){ + *n1 = width; + *n2 = height; + }; +} + ++ (NSString*)parseVideoStreamSampleAspectRatio: (NSString*)input { + if (input != nil) { + + NSString *formattedString = [input lowercaseString]; + formattedString = [formattedString stringByReplacingOccurrencesOfString:@"[" withString:@""]; + formattedString = [formattedString stringByReplacingOccurrencesOfString:@"]" withString:@""]; + + NSArray* parts = [formattedString componentsSeparatedByString:@" "]; + + for (int i=0; i < [parts count]; i++) { + NSString *token = [parts objectAtIndex:i]; + if ([token isEqualToString:@"sar"]) { + return [MediaInformationParser safeGet:parts from:(i + 1)]; + } + } + } + + return nil; +} + ++ (NSString*)parseVideoStreamDisplayAspectRatio: (NSString*)input { + if (input != nil) { + + NSString *formattedString = [input lowercaseString]; + formattedString = [formattedString stringByReplacingOccurrencesOfString:@"[" withString:@""]; + formattedString = [formattedString stringByReplacingOccurrencesOfString:@"]" withString:@""]; + + NSArray* parts = [formattedString componentsSeparatedByString:@" "]; + + for (int i=0; i < [parts count]; i++) { + NSString *token = [parts objectAtIndex:i]; + if ([token isEqualToString:@"dar"]) { + return [MediaInformationParser safeGet:parts from:(i + 1)]; + } + } + } + + return nil; +} + ++ (NSNumber*)parseAudioStreamSampleRate: (NSString*)input { + if (input != nil) { + Boolean khz = false; + Boolean mhz = false; + NSString *lowerCaseString = [input lowercaseString]; + + if ([lowerCaseString rangeOfString:@"khz"].location != NSNotFound) { + khz = true; + } + if ([lowerCaseString rangeOfString:@"mhz"].location != NSNotFound) { + mhz = true; + } + + lowerCaseString = [lowerCaseString stringByReplacingOccurrencesOfString:@"khz" withString:@""]; + lowerCaseString = [lowerCaseString stringByReplacingOccurrencesOfString:@"mhz" withString:@""]; + lowerCaseString = [lowerCaseString stringByReplacingOccurrencesOfString:@"hz" withString:@""]; + + NSNumber *sampleRate = [MediaInformationParser toInteger:[lowerCaseString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]]; + if (khz) { + return [NSNumber numberWithInteger:(1000*sampleRate.intValue)]; + } else if (mhz) { + return [NSNumber numberWithInteger:(1000000*sampleRate.intValue)]; + } else { + return sampleRate; + } + } + + return nil; +} + ++ (NSString*)parseStreamType: (NSString*)input { + if (input != nil) { + NSString *formattedString = [input lowercaseString]; + + if ([formattedString rangeOfString:@"audio:"].location != NSNotFound) { + return @"audio"; + } else if ([formattedString rangeOfString:@"video:"].location != NSNotFound) { + return @"video"; + } + } + + return nil; +} + ++ (NSString*)parseStreamCodec: (NSString*)input { + if (input != nil) { + NSString *formattedString = [input lowercaseString]; + formattedString = [parenthesesRegex stringByReplacingMatchesInString:formattedString options:0 range:NSMakeRange(0, [formattedString length]) withTemplate:@""]; + formattedString = [formattedString stringByReplacingOccurrencesOfString:@"video:" withString:@""]; + formattedString = [formattedString stringByReplacingOccurrencesOfString:@"audio:" withString:@""]; + + return [formattedString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + } + + return nil; +} + ++ (NSString*)parseStreamFullCodec: (NSString*)input { + if (input != nil) { + NSString *formattedString = [input lowercaseString]; + formattedString = [formattedString stringByReplacingOccurrencesOfString:@"video:" withString:@""]; + formattedString = [formattedString stringByReplacingOccurrencesOfString:@"audio:" withString:@""]; + + return [formattedString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + } + + return nil; +} + ++ (NSNumber*)parseStreamIndex: (NSString*)input { + NSString *substring = [MediaInformationParser substring:input from:@"Stream #0:" to:@":" ignoring:[[NSMutableArray alloc] initWithObjects:@"tream #0", nil]]; + if (substring != nil) { + substring = [substring stringByReplacingOccurrencesOfString:@":" withString:@""]; + substring = [parenthesesRegex stringByReplacingMatchesInString:substring options:0 range:NSMakeRange(0, [substring length]) withTemplate:@""]; + + return [MediaInformationParser toIntegerObject:substring]; + } + + return nil; +} + ++ (NSNumber*)parseDuration: (NSString*)input { + if (input != nil && ![input isEqualToString:@"N/A"]) { + NSString *seconds = [MediaInformationParser substring:input to:@"." ignoring:[[NSMutableArray alloc] init]]; + if (seconds != nil) { + NSDate* durationDate = [durationFormat dateFromString: seconds]; + if (durationDate != nil) { + NSTimeInterval secondsInMilliseconds = [durationDate timeIntervalSinceDate:referenceDuration]*1000; + NSNumber *centiSeconds = [MediaInformationParser toInteger:[MediaInformationParser substring:input from:@"." ignoring:[[NSMutableArray alloc] init]]]; + + secondsInMilliseconds += centiSeconds.intValue*10; + + return [NSNumber numberWithInteger:secondsInMilliseconds]; + } + } + } + + return nil; +} + ++ (NSNumber*)parseStartTime: (NSString*)input { + if (input != nil && ![input isEqualToString:@"N/A"] && ([input length]>0)) { + double inputAsDouble = [input doubleValue]; + return [NSNumber numberWithInteger:ceil(inputAsDouble*1000)]; + } + + return nil; +} + ++ (NSString*)substring:(NSString*)string from:(NSString*)start to:(NSString*)end ignoring:(NSArray*)ignoredTokens { + NSString *extractedSubstring = nil; + + if (string != nil) { + NSRange formatStart = [string rangeOfString:start]; + if (formatStart.location != NSNotFound && (formatStart.location + start.length < [string length])) { + + NSRange formatEnd = [string rangeOfString:end options:NSLiteralSearch range:NSMakeRange(formatStart.location + start.length, string.length - (formatStart.location + start.length + 1))]; + + if (formatEnd.location != NSNotFound) { + extractedSubstring = [string substringWithRange:NSMakeRange(formatStart.location + start.length, formatEnd.location - (formatStart.location + start.length))]; + } + } + } + + if ((ignoredTokens != nil) && (extractedSubstring != nil)) { + for (int i=0; i < [ignoredTokens count]; i++) { + NSString *token = [ignoredTokens objectAtIndex:i]; + extractedSubstring = [extractedSubstring stringByReplacingOccurrencesOfString:token withString:@""]; + } + } + + return (extractedSubstring == nil) ? nil : [extractedSubstring stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; +} + ++ (NSString*)substring:(NSString*)string from:(NSString*)start ignoring:(NSArray*)ignoredTokens { + NSString *extractedSubstring = nil; + + if (string != nil) { + NSRange formatStart = [string rangeOfString:start]; + if (formatStart.location != NSNotFound) { + extractedSubstring = [string substringWithRange:NSMakeRange(formatStart.location + start.length, string.length - (formatStart.location + start.length))]; + } + } + + if ((ignoredTokens != nil) && (extractedSubstring != nil)) { + for (int i=0; i < [ignoredTokens count]; i++) { + NSString *token = [ignoredTokens objectAtIndex:i]; + extractedSubstring = [extractedSubstring stringByReplacingOccurrencesOfString:token withString:@""]; + } + } + + return (extractedSubstring == nil) ? nil : [extractedSubstring stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; +} + ++ (NSString*)substring:(NSString*)string to:(NSString*)start ignoring:(NSArray*)ignoredTokens { + NSString *extractedSubstring = nil; + + if (string != nil) { + NSRange formatStart = [string rangeOfString:start]; + if (formatStart.location != NSNotFound) { + extractedSubstring = [string substringWithRange:NSMakeRange(0, formatStart.location)]; + } + } + + if ((ignoredTokens != nil) && (extractedSubstring != nil)) { + for (int i=0; i < [ignoredTokens count]; i++) { + NSString *token = [ignoredTokens objectAtIndex:i]; + extractedSubstring = [extractedSubstring stringByReplacingOccurrencesOfString:token withString:@""]; + } + } + + return (extractedSubstring == nil) ? nil : [extractedSubstring stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; +} + ++ (int)index:(NSString*)string of:(NSString*)substring from:(int)startIndex times:(int)n { + int count = 1; + startIndex -= substring.length; + + while (count <= n) { + unsigned long searchIndex = startIndex + (int)substring.length; + NSRange currentRange = [string rangeOfString:substring options:NSLiteralSearch range:NSMakeRange(searchIndex, string.length - searchIndex)]; + if (currentRange.location != NSNotFound) { + startIndex = (int)currentRange.location; + } + + count++; + } + + return startIndex; +} + ++ (int)count:(NSString*)string of:(NSString*)substring { + int count = 0; + int index = 0 - (int)substring.length; + + do { + int searchIndex = index + (int)substring.length; + NSRange currentRange = [string rangeOfString:substring options:NSLiteralSearch range:NSMakeRange(searchIndex, string.length - searchIndex)]; + if (currentRange.location != NSNotFound) { + count++; + index = (int)currentRange.location; + } else { + index = -1; + } + } while (index >= 0); + + return count; +} + ++ (NSNumber*)toInteger: (NSString*)input { + if (input == nil) { + return [NSNumber numberWithInt:0]; + } + + return [NSNumber numberWithInteger:input.integerValue]; +} + ++ (NSNumber*)toIntegerObject: (NSString*)input { + if (input == nil) { + return nil; + } + + return [NSNumber numberWithInteger:input.integerValue]; +} + ++ (NSString*)safeGet:(NSArray*)array from:(int)index { + if (array == nil) { + return nil; + } + + unsigned long size = [array count]; + if (size > index) { + return [array objectAtIndex:index]; + } else { + return nil; + } +} + +@end diff --git a/ios/src/StreamInformation.h b/ios/src/StreamInformation.h new file mode 100644 index 000000000..f92492fdc --- /dev/null +++ b/ios/src/StreamInformation.h @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2018 Taner Sener + * + * This file is part of MobileFFmpeg. + * + * MobileFFmpeg is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MobileFFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MobileFFmpeg. If not, see . + */ + +#include + +@interface StreamInformation : NSObject + +- (instancetype)init; + +/** + * Returns stream index. + * + * \return stream index, starting from zero + */ +- (NSNumber*)getIndex; + +/** + * Sets stream index. + * + * \param stream index, starting from zero + */ +- (void)setIndex:(NSNumber*) index; + +/** + * Returns stream type. + * + * \return stream type; audio or video + */ +- (NSString*)getType; + +/** + * Sets stream type. + * + * \param stream type; audio or video + */ +- (void)setType:(NSString*) type; + +/** + * Returns stream codec. + * + * \return stream codec + */ +- (NSString*)getCodec; + +/** + * Sets stream codec. + * + * \param stream codec + */ +- (void)setCodec:(NSString*) codec; + +/** + * Returns full stream codec. + * + * \return stream codec with additional profile and mode information + */ +- (NSString*)getFullCodec; + +/** + * Sets full stream codec. + * + * \param stream codec with additional profile and mode information + */ +- (void)setFullCodec:(NSString*) fullCodec; + +/** + * Returns stream format. + * + * \return stream format + */ +- (NSString*)getFormat; + +/** + * Sets stream format. + * + * \param stream format + */ +- (void)setFormat:(NSString*) format; + +/** + * Returns full stream format. + * + * \return stream format with + */ +- (NSString*)getFullFormat; + +/** + * Sets full stream format. + * + * \param stream format with + */ +- (void)setFullFormat:(NSString*) fullFormat; + +/** + * Returns width. + * + * \return width in pixels + */ +- (NSNumber*)getWidth; + +/** + * Sets width. + * + * \param width in pixels + */ +- (void)setWidth:(NSNumber*) width; + +/** + * Returns height. + * + * \return height in pixels + */ +- (NSNumber*)getHeight; + +/** + * Sets height. + * + * \param height in pixels + */ +- (void)setHeight:(NSNumber*) height; + +/** + * Returns bitrate. + * + * \return bitrate in kb/s + */ +- (NSNumber*)getBitrate; + +/** + * Sets bitrate. + * + * \param bitrate in kb/s + */ +- (void)setBitrate:(NSNumber*) bitrate; + +/** + * Returns sample rate. + * + * \return sample rate in hz + */ +- (NSNumber*)getSampleRate; + +/** + * Sets sample rate. + * + * \param sample rate in hz + */ +- (void)setSampleRate:(NSNumber*) sampleRate; + +/** + * Returns sample format. + * + * \return sample format + */ +- (NSString*)getSampleFormat; + +/** + * Sets sample format. + * + * \param sample format + */ +- (void)setSampleFormat:(NSString*) sampleFormat; + +/** + * Returns channel layout. + * + * \return channel layout + */ +- (NSString*)getChannelLayout; + +/** + * Sets channel layout. + * + * \param channel layout + */ +- (void)setChannelLayout:(NSString*) channelLayout; + +/** + * Returns sample aspect ratio. + * + * \return sample aspect ratio + */ +- (NSString*)getSampleAspectRatio; + +/** + * Sets sample aspect ratio. + * + * \param sample aspect ratio + */ +- (void)setSampleAspectRatio:(NSString*) sampleAspectRatio; + +/** + * Returns display aspect ratio. + * + * \return display aspect ratio + */ +- (NSString*)getDisplayAspectRatio; + +/** + * Sets display aspect ratio. + * + * \param display aspect ratio + */ +- (void)setDisplayAspectRatio:(NSString*) displayAspectRatio; + +/** + * Returns average frame rate. + * + * \return average frame rate in fps + */ +- (NSString*)getAverageFrameRate; + +/** + * Sets average frame rate. + * + * \param average frame rate in fps + */ +- (void)setAverageFrameRate:(NSString*) averageFrameRatehn; + +/** + * Returns real frame rate. + * + * \return real frame rate in tbr + */ +- (NSString*)getRealFrameRate; + +/** + * Sets real frame rate. + * + * \param real frame rate in tbr + */ +- (void)setRealFrameRate:(NSString*) realFrameRate; + +/** + * Returns time base. + * + * \return time base in tbn + */ +- (NSString*)getTimeBase; + +/** + * Sets time base. + * + * \param time base in tbn + */ +- (void)setTimeBase:(NSString*) timeBase; + +/** + * Returns codec time base. + * + * \return codec time base in tbc + */ +- (NSString*)getCodecTimeBase; + +/** + * Sets codec time base. + * + * \param codec time base in tbc + */ +- (void)setCodecTimeBase:(NSString*) codecTimeBase; + +/** + * Adds metadata. + * + * \param metadata key and value + */ +- (void)addMetadata:(NSString*)key :(NSString*)value; + +/** + * Returns all metadata entries. + * + * \return metadata dictionary + */ +- (NSDictionary*)getMetadataEntries; + +@end diff --git a/ios/src/StreamInformation.m b/ios/src/StreamInformation.m new file mode 100644 index 000000000..a422f8e1a --- /dev/null +++ b/ios/src/StreamInformation.m @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2018 Taner Sener + * + * This file is part of MobileFFmpeg. + * + * MobileFFmpeg is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MobileFFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MobileFFmpeg. If not, see . + */ + +#include "StreamInformation.h" + +@implementation StreamInformation { + + /** + * Stream index + */ + NSNumber *index; + + NSString *type; + NSString *codec; + NSString *fullCodec; + NSString *format; + NSString *fullFormat; + + NSNumber *width; + NSNumber *height; + + NSNumber *bitrate; + NSNumber *sampleRate; + NSString *sampleFormat; + NSString *channelLayout; + + /** + * SAR + */ + NSString *sampleAspectRatio; + + /** + * DAR + */ + NSString *displayAspectRatio; + + /** + * fps + */ + NSString *averageFrameRate; + + /** + * tbr + */ + NSString *realFrameRate; + + /** + * tbn + */ + NSString *timeBase; + + /** + * tbc + */ + NSString *codecTimeBase; + + /** + * Metadata map + */ + NSMutableDictionary *metadata; + +} + +- (instancetype)init { + self = [super init]; + if (self) { + index = nil; + type = nil; + codec = nil; + fullCodec = nil; + format = nil; + fullFormat = nil; + width = nil; + height = nil; + bitrate = nil; + sampleRate = nil; + sampleFormat = nil; + channelLayout = nil; + sampleAspectRatio = nil; + displayAspectRatio = nil; + averageFrameRate = nil; + realFrameRate = nil; + timeBase = nil; + codecTimeBase = nil; + metadata = nil; + } + + return self; +} + +- (NSNumber*)getIndex { + return index; +} + +- (void)setIndex:(NSNumber*) newIndex { + index = newIndex; +} + +- (NSString*)getType { + return type; +} + +- (void)setType:(NSString*) newType { + type = newType; +} + +- (NSString*)getCodec { + return codec; +} + +- (void)setCodec:(NSString*) newCodec { + codec = newCodec; +} + +- (NSString*)getFullCodec { + return fullCodec; +} + +- (void)setFullCodec:(NSString*) newFullCodec { + fullCodec = newFullCodec; +} + +- (NSString*)getFormat { + return format; +} + +- (void)setFormat:(NSString*) newFormat { + format = newFormat; +} + +- (NSString*)getFullFormat { + return fullFormat; +} + +- (void)setFullFormat:(NSString*) newFullFormat { + fullFormat = newFullFormat; +} + +- (NSNumber*)getWidth { + return width; +} + +- (void)setWidth:(NSNumber*) newWidth { + width = newWidth; +} + +- (NSNumber*)getHeight { + return height; +} + +- (void)setHeight:(NSNumber*) newHeight { + height = newHeight; +} + +- (NSNumber*)getBitrate { + return bitrate; +} + +- (void)setBitrate:(NSNumber*) newBitrate { + bitrate = newBitrate; +} + +- (NSNumber*)getSampleRate { + return sampleRate; +} + +- (void)setSampleRate:(NSNumber*) newSampleRate { + sampleRate = newSampleRate; +} + +- (NSString*)getSampleFormat { + return sampleFormat; +} + +- (void)setSampleFormat:(NSString*) newSampleFormat { + sampleFormat = newSampleFormat; +} + +- (NSString*)getChannelLayout { + return channelLayout; +} + +- (void)setChannelLayout:(NSString*) newChannelLayout { + channelLayout = newChannelLayout; +} + +- (NSString*)getSampleAspectRatio { + return sampleAspectRatio; +} + +- (void)setSampleAspectRatio:(NSString*) newSampleAspectRatio { + sampleAspectRatio = newSampleAspectRatio; +} + +- (NSString*)getDisplayAspectRatio { + return displayAspectRatio; +} + +- (void)setDisplayAspectRatio:(NSString*) newDisplayAspectRatio { + displayAspectRatio = newDisplayAspectRatio; +} + +- (NSString*)getAverageFrameRate { + return averageFrameRate; +} + +- (void)setAverageFrameRate:(NSString*) newAverageFrameRatehn { + averageFrameRate = newAverageFrameRatehn; +} + +- (NSString*)getRealFrameRate { + return realFrameRate; +} + +- (void)setRealFrameRate:(NSString*) newRealFrameRate { + realFrameRate = newRealFrameRate; +} + +- (NSString*)getTimeBase { + return timeBase; +} + +- (void)setTimeBase:(NSString*) newTimeBase { + timeBase = newTimeBase; +} + +- (NSString*)getCodecTimeBase { + return codecTimeBase; +} + +- (void)setCodecTimeBase:(NSString*) newCodecTimeBase { + codecTimeBase = newCodecTimeBase; +} + +- (void)addMetadata:(NSString*)key :(NSString*)value { + metadata[key] = value; +} + +- (NSDictionary*)getMetadataEntries { + return metadata; +} + +@end diff --git a/ios/test-app/MobileFFmpegTest.xcodeproj/project.pbxproj b/ios/test-app/MobileFFmpegTest.xcodeproj/project.pbxproj index ff07d0f2a..a1551023e 100644 --- a/ios/test-app/MobileFFmpegTest.xcodeproj/project.pbxproj +++ b/ios/test-app/MobileFFmpegTest.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ 340FABBE2115A17600B33CE7 /* VidStabViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 340FABBC2115A17600B33CE7 /* VidStabViewController.h */; }; 340FABBF2115A17600B33CE7 /* VidStabViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FABBD2115A17600B33CE7 /* VidStabViewController.m */; }; + 3423D267217F7CC100C3C7ED /* MediaInformationParserTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3423D266217F7CC100C3C7ED /* MediaInformationParserTests.m */; }; + 3423D269217F7D4E00C3C7ED /* MediaInformationParserTests.h in Headers */ = {isa = PBXBuildFile; fileRef = 3423D268217F7D4E00C3C7ED /* MediaInformationParserTests.h */; }; 343E66B5210784BD00F95E5B /* HttpsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 343E66B3210784BD00F95E5B /* HttpsViewController.h */; }; 343E66B6210784BD00F95E5B /* HttpsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 343E66B4210784BD00F95E5B /* HttpsViewController.m */; }; 343E66B9210789DE00F95E5B /* Constants.h in Headers */ = {isa = PBXBuildFile; fileRef = 343E66B7210789DE00F95E5B /* Constants.h */; }; @@ -24,6 +26,22 @@ 348439EE20B2EFAF001A3990 /* tajmahal.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 348439EB20B2EFAF001A3990 /* tajmahal.jpg */; }; 348439EF20B2EFAF001A3990 /* colosseum.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 348439EC20B2EFAF001A3990 /* colosseum.jpg */; }; 34BB547C2109D44800606B7C /* Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = 34BB547B2109D44800606B7C /* Constants.m */; }; + 34C5E89E217F7F86001DCD99 /* libavfilter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E896217F7F86001DCD99 /* libavfilter.framework */; }; + 34C5E89F217F7F86001DCD99 /* libavcodec.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E897217F7F86001DCD99 /* libavcodec.framework */; }; + 34C5E8A0217F7F86001DCD99 /* libswresample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E898217F7F86001DCD99 /* libswresample.framework */; }; + 34C5E8A1217F7F86001DCD99 /* libavutil.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E899217F7F86001DCD99 /* libavutil.framework */; }; + 34C5E8A2217F7F86001DCD99 /* libswscale.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E89A217F7F86001DCD99 /* libswscale.framework */; }; + 34C5E8A3217F7F86001DCD99 /* libavformat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E89B217F7F86001DCD99 /* libavformat.framework */; }; + 34C5E8A4217F7F86001DCD99 /* mobileffmpeg.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E89C217F7F86001DCD99 /* mobileffmpeg.framework */; }; + 34C5E8A5217F7F86001DCD99 /* libavdevice.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E89D217F7F86001DCD99 /* libavdevice.framework */; }; + 34C5E8A6217F7F92001DCD99 /* libavcodec.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E897217F7F86001DCD99 /* libavcodec.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 34C5E8A7217F7F92001DCD99 /* libavdevice.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E89D217F7F86001DCD99 /* libavdevice.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 34C5E8A8217F7F92001DCD99 /* libavfilter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E896217F7F86001DCD99 /* libavfilter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 34C5E8A9217F7F92001DCD99 /* libavformat.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E89B217F7F86001DCD99 /* libavformat.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 34C5E8AA217F7F92001DCD99 /* libavutil.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E899217F7F86001DCD99 /* libavutil.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 34C5E8AB217F7F92001DCD99 /* libswresample.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E898217F7F86001DCD99 /* libswresample.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 34C5E8AC217F7F92001DCD99 /* libswscale.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E89A217F7F86001DCD99 /* libswscale.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 34C5E8AD217F7F92001DCD99 /* mobileffmpeg.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 34C5E89C217F7F86001DCD99 /* mobileffmpeg.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 34DC789B21215B5800C3486C /* truenorg.otf in Resources */ = {isa = PBXBuildFile; fileRef = 34DC789721215B5800C3486C /* truenorg.otf */; }; 34DC789C21215B5800C3486C /* subtitle.srt in Resources */ = {isa = PBXBuildFile; fileRef = 34DC789821215B5800C3486C /* subtitle.srt */; }; 34DC789D21215B5800C3486C /* doppioone_regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 34DC789921215B5800C3486C /* doppioone_regular.ttf */; }; @@ -45,6 +63,14 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + 34C5E8A6217F7F92001DCD99 /* libavcodec.framework in Embed Frameworks */, + 34C5E8A7217F7F92001DCD99 /* libavdevice.framework in Embed Frameworks */, + 34C5E8A8217F7F92001DCD99 /* libavfilter.framework in Embed Frameworks */, + 34C5E8A9217F7F92001DCD99 /* libavformat.framework in Embed Frameworks */, + 34C5E8AA217F7F92001DCD99 /* libavutil.framework in Embed Frameworks */, + 34C5E8AB217F7F92001DCD99 /* libswresample.framework in Embed Frameworks */, + 34C5E8AC217F7F92001DCD99 /* libswscale.framework in Embed Frameworks */, + 34C5E8AD217F7F92001DCD99 /* mobileffmpeg.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -55,6 +81,8 @@ 09762EB4030C758C5727BD68 /* Pods-MobileFFmpegTest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MobileFFmpegTest.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MobileFFmpegTest/Pods-MobileFFmpegTest.debug.xcconfig"; sourceTree = ""; }; 340FABBC2115A17600B33CE7 /* VidStabViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VidStabViewController.h; sourceTree = ""; }; 340FABBD2115A17600B33CE7 /* VidStabViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VidStabViewController.m; sourceTree = ""; }; + 3423D266217F7CC100C3C7ED /* MediaInformationParserTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MediaInformationParserTests.m; sourceTree = ""; }; + 3423D268217F7D4E00C3C7ED /* MediaInformationParserTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaInformationParserTests.h; sourceTree = ""; }; 343E66B3210784BD00F95E5B /* HttpsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HttpsViewController.h; sourceTree = ""; }; 343E66B4210784BD00F95E5B /* HttpsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HttpsViewController.m; sourceTree = ""; }; 343E66B7210789DE00F95E5B /* Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = ""; }; @@ -70,6 +98,14 @@ 348439EB20B2EFAF001A3990 /* tajmahal.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = tajmahal.jpg; sourceTree = ""; }; 348439EC20B2EFAF001A3990 /* colosseum.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = colosseum.jpg; sourceTree = ""; }; 34BB547B2109D44800606B7C /* Constants.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Constants.m; sourceTree = ""; }; + 34C5E896217F7F86001DCD99 /* libavfilter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = libavfilter.framework; sourceTree = ""; }; + 34C5E897217F7F86001DCD99 /* libavcodec.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = libavcodec.framework; sourceTree = ""; }; + 34C5E898217F7F86001DCD99 /* libswresample.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = libswresample.framework; sourceTree = ""; }; + 34C5E899217F7F86001DCD99 /* libavutil.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = libavutil.framework; sourceTree = ""; }; + 34C5E89A217F7F86001DCD99 /* libswscale.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = libswscale.framework; sourceTree = ""; }; + 34C5E89B217F7F86001DCD99 /* libavformat.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = libavformat.framework; sourceTree = ""; }; + 34C5E89C217F7F86001DCD99 /* mobileffmpeg.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = mobileffmpeg.framework; sourceTree = ""; }; + 34C5E89D217F7F86001DCD99 /* libavdevice.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = libavdevice.framework; sourceTree = ""; }; 34DC789721215B5800C3486C /* truenorg.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = truenorg.otf; sourceTree = ""; }; 34DC789821215B5800C3486C /* subtitle.srt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = subtitle.srt; sourceTree = ""; }; 34DC789921215B5800C3486C /* doppioone_regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = doppioone_regular.ttf; sourceTree = ""; }; @@ -95,7 +131,15 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 34C5E8A1217F7F86001DCD99 /* libavutil.framework in Frameworks */, + 34C5E89F217F7F86001DCD99 /* libavcodec.framework in Frameworks */, + 34C5E8A0217F7F86001DCD99 /* libswresample.framework in Frameworks */, + 34C5E8A2217F7F86001DCD99 /* libswscale.framework in Frameworks */, + 34C5E8A5217F7F86001DCD99 /* libavdevice.framework in Frameworks */, 8A776E4D8F4EB85888ADFB99 /* Pods_MobileFFmpegTest.framework in Frameworks */, + 34C5E89E217F7F86001DCD99 /* libavfilter.framework in Frameworks */, + 34C5E8A3217F7F86001DCD99 /* libavformat.framework in Frameworks */, + 34C5E8A4217F7F86001DCD99 /* mobileffmpeg.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -119,6 +163,14 @@ 3498DC65209F7F1C005F5883 /* Frameworks */ = { isa = PBXGroup; children = ( + 34C5E897217F7F86001DCD99 /* libavcodec.framework */, + 34C5E89D217F7F86001DCD99 /* libavdevice.framework */, + 34C5E896217F7F86001DCD99 /* libavfilter.framework */, + 34C5E89B217F7F86001DCD99 /* libavformat.framework */, + 34C5E899217F7F86001DCD99 /* libavutil.framework */, + 34C5E898217F7F86001DCD99 /* libswresample.framework */, + 34C5E89A217F7F86001DCD99 /* libswscale.framework */, + 34C5E89C217F7F86001DCD99 /* mobileffmpeg.framework */, F4B824D376F9CD476C761D40 /* Pods_MobileFFmpegTest.framework */, ); name = Frameworks; @@ -147,30 +199,32 @@ isa = PBXGroup; children = ( 34FAE213209F7DDD005CE2AE /* AppDelegate.h */, - 345745492113B8AD00059043 /* AudioViewController.h */, - 34FAE216209F7DDD005CE2AE /* CommandViewController.h */, - 343E66B7210789DE00F95E5B /* Constants.h */, - 343E66B3210784BD00F95E5B /* HttpsViewController.h */, - 3457454D2113B8D300059043 /* SubtitleViewController.h */, - 343E67072107C27600F95E5B /* TabBarController.h */, - 343E67032107AD0100F95E5B /* Util.h */, - 34FAE219209F7DDD005CE2AE /* VideoViewController.h */, - 340FABBC2115A17600B33CE7 /* VidStabViewController.h */, 34FAE214209F7DDD005CE2AE /* AppDelegate.m */, - 3457454A2113B8AD00059043 /* AudioViewController.m */, - 34FAE217209F7DDD005CE2AE /* CommandViewController.m */, - 34BB547B2109D44800606B7C /* Constants.m */, - 343E66B4210784BD00F95E5B /* HttpsViewController.m */, - 34FAE225209F7DDE005CE2AE /* main.m */, - 3457454E2113B8D300059043 /* SubtitleViewController.m */, - 343E67082107C27600F95E5B /* TabBarController.m */, - 343E67042107AD0100F95E5B /* Util.m */, - 34FAE21A209F7DDD005CE2AE /* VideoViewController.m */, - 340FABBD2115A17600B33CE7 /* VidStabViewController.m */, - 34FAE224209F7DDE005CE2AE /* Info.plist */, 34FAE21F209F7DDE005CE2AE /* Assets.xcassets */, + 345745492113B8AD00059043 /* AudioViewController.h */, + 3457454A2113B8AD00059043 /* AudioViewController.m */, + 34FAE216209F7DDD005CE2AE /* CommandViewController.h */, + 34FAE217209F7DDD005CE2AE /* CommandViewController.m */, + 343E66B7210789DE00F95E5B /* Constants.h */, + 34BB547B2109D44800606B7C /* Constants.m */, + 343E66B3210784BD00F95E5B /* HttpsViewController.h */, + 343E66B4210784BD00F95E5B /* HttpsViewController.m */, + 34FAE224209F7DDE005CE2AE /* Info.plist */, 34FAE221209F7DDE005CE2AE /* LaunchScreen.storyboard */, + 34FAE225209F7DDE005CE2AE /* main.m */, 34FAE21C209F7DDD005CE2AE /* Main.storyboard */, + 3423D268217F7D4E00C3C7ED /* MediaInformationParserTests.h */, + 3423D266217F7CC100C3C7ED /* MediaInformationParserTests.m */, + 3457454D2113B8D300059043 /* SubtitleViewController.h */, + 3457454E2113B8D300059043 /* SubtitleViewController.m */, + 343E67072107C27600F95E5B /* TabBarController.h */, + 343E67082107C27600F95E5B /* TabBarController.m */, + 343E67032107AD0100F95E5B /* Util.h */, + 343E67042107AD0100F95E5B /* Util.m */, + 34FAE219209F7DDD005CE2AE /* VideoViewController.h */, + 34FAE21A209F7DDD005CE2AE /* VideoViewController.m */, + 340FABBC2115A17600B33CE7 /* VidStabViewController.h */, + 340FABBD2115A17600B33CE7 /* VidStabViewController.m */, ); path = MobileFFmpegTest; sourceTree = ""; @@ -192,6 +246,7 @@ buildActionMask = 2147483647; files = ( 343E67092107C27600F95E5B /* TabBarController.h in Headers */, + 3423D269217F7D4E00C3C7ED /* MediaInformationParserTests.h in Headers */, 3457454B2113B8AD00059043 /* AudioViewController.h in Headers */, 3457454F2113B8D300059043 /* SubtitleViewController.h in Headers */, 343E66B9210789DE00F95E5B /* Constants.h in Headers */, @@ -345,6 +400,7 @@ 343E66B6210784BD00F95E5B /* HttpsViewController.m in Sources */, 340FABBF2115A17600B33CE7 /* VidStabViewController.m in Sources */, 34FAE218209F7DDD005CE2AE /* CommandViewController.m in Sources */, + 3423D267217F7CC100C3C7ED /* MediaInformationParserTests.m in Sources */, 343E670A2107C27600F95E5B /* TabBarController.m in Sources */, 345745502113B8D300059043 /* SubtitleViewController.m in Sources */, 343E67062107AD0100F95E5B /* Util.m in Sources */, diff --git a/ios/test-app/MobileFFmpegTest.xcworkspace/xcuserdata/taner.xcuserdatad/UserInterfaceState.xcuserstate b/ios/test-app/MobileFFmpegTest.xcworkspace/xcuserdata/taner.xcuserdatad/UserInterfaceState.xcuserstate index 6240bb8fa..f79875b88 100644 Binary files a/ios/test-app/MobileFFmpegTest.xcworkspace/xcuserdata/taner.xcuserdatad/UserInterfaceState.xcuserstate and b/ios/test-app/MobileFFmpegTest.xcworkspace/xcuserdata/taner.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/ios/test-app/MobileFFmpegTest/MediaInformationParserTests.h b/ios/test-app/MobileFFmpegTest/MediaInformationParserTests.h new file mode 100644 index 000000000..776451398 --- /dev/null +++ b/ios/test-app/MobileFFmpegTest/MediaInformationParserTests.h @@ -0,0 +1,25 @@ +// +// MediaInformationParserTests.h +// +// Copyright (c) 2018 Taner Sener +// +// This file is part of MobileFFmpeg. +// +// MobileFFmpeg is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// MobileFFmpeg is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with MobileFFmpeg. If not, see . +// + +/** + * All parsing tests are initiated from this method + */ +void runMediaInformationParserTests(); diff --git a/ios/test-app/MobileFFmpegTest/MediaInformationParserTests.m b/ios/test-app/MobileFFmpegTest/MediaInformationParserTests.m new file mode 100644 index 000000000..4ebdad7a7 --- /dev/null +++ b/ios/test-app/MobileFFmpegTest/MediaInformationParserTests.m @@ -0,0 +1,841 @@ +// +// MediaInformationParserTests.m +// +// Copyright (c) 2018 Taner Sener +// +// This file is part of MobileFFmpeg. +// +// MobileFFmpeg is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// MobileFFmpeg is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with MobileFFmpeg. If not, see . +// + +#include + +#include + +static NSString *MEDIA_INFORMATION_MP3; +static NSString *MEDIA_INFORMATION_JPG; +static NSString *MEDIA_INFORMATION_GIF; +static NSString *MEDIA_INFORMATION_H264; +static NSString *MEDIA_INFORMATION_PNG; +static NSString *MEDIA_INFORMATION_H264_2; +static NSString *MEDIA_INFORMATION_MP4; +static NSString *MEDIA_INFORMATION_MP4_2; +static NSString *MEDIA_INFORMATION_OGG; + +static void initTests() { + MEDIA_INFORMATION_MP3 = [NSString stringWithFormat: + @"Unknown attached picture mimetype: audio/x-wav, skipping.\n" + "[mp3 @ 0x7ffb94805800] Estimating duration from bitrate, this may be inaccurate\n" + "Input #0, mp3, from 'beethoven_-_symphony_no_9.mp3':\n" + " Metadata:\n" + " comment : \n" + " album : Symphony No.9\n" + " compilation : 0\n" + " date : -1\n" + " title : Symphony No.9\n" + " artist : Beethoven\n" + " album_artist : Beethoven\n" + " track : -1\n" + " lyrics-XXX : \n" + " Duration: 00:03:33.24, start: 0.000000, bitrate: 320 kb/s\n" + " Stream #0:0: Audio: mp3, 48000 Hz, stereo, fltp, 320 kb/s\n" + "Stream mapping:\n" + " Stream #0:0 -> #0:0 (mp3 (mp3float) -> pcm_s16le (native))\n" + "Press [q] to stop, [?] for help\n" + "Output #0, null, to 'pipe:':\n" + " Metadata:\n" + " comment : \n" + " album : Symphony No.9\n" + " compilation : 0\n" + " date : -1\n" + " title : Symphony No.9\n" + " artist : Beethoven\n" + " album_artist : Beethoven\n" + " track : -1\n" + " lyrics-XXX : \n" + " encoder : Lavf58.12.100\n" + " Stream #0:0: Audio: pcm_s16le, 48000 Hz, stereo, s16, 1536 kb/s\n" + " Metadata:\n" + " encoder : Lavc58.18.100 pcm_s16le\n" + "size=N/A time=00:03:33.24 bitrate=N/A speed= 618x \n" + "video:0kB audio:39982kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown"]; + + MEDIA_INFORMATION_JPG = [NSString stringWithFormat: + @"Input #0, image2, from '/data/user/0/com.arthenica.mobileffmpeg.test/cache/colosseum.jpg':\n" + " Duration: 00:00:00.04, start: 0.000000, bitrate: 391187 kb/s\n" + " Stream #0:0: Video: mjpeg, yuvj420p(pc, bt470bg/unknown/unknown), 2560x1708 [SAR 1:1 DAR 640:427], 25 tbr, 25 tbn, 25 tbc\n" + "Stream mapping:\n" + " Stream #0:0 -> #0:0 (mjpeg (native) -> wrapped_avframe (native))\n" + "Press [q] to stop, [?] for help\n" + "Output #0, null, to 'pipe:':\n" + " Metadata:\n" + " encoder : Lavf58.18.104\n" + " Stream #0:0: Video: wrapped_avframe, yuvj420p, 2560x1708 [SAR 1:1 DAR 640:427], q=2-31, 200 kb/s, 25 fps, 25 tbn, 25 tbc\n" + " Metadata:\n" + " encoder : Lavc58.31.102 wrapped_avframe\n" + "frame= 1 fps=0.0 q=-0.0 Lsize=N/A time=00:00:00.04 bitrate=N/A speed=0.668x \n" + "video:0kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown\n"]; + + MEDIA_INFORMATION_GIF = [NSString stringWithFormat: + @"Input #0, gif, from 'advanced_zoom_in_and_pan_with_fade_in_out.gif':\n" + " Duration: N/A, bitrate: N/A\n" + " Stream #0:0: Video: gif, bgra, 420x236, 6 fps, 6 tbr, 100 tbn, 100 tbc\n" + "Stream mapping:\n" + " Stream #0:0 -> #0:0 (gif (native) -> wrapped_avframe (native))\n" + "Press [q] to stop, [?] for help\n" + "Output #0, null, to 'pipe:':\n" + " Metadata:\n" + " encoder : Lavf58.12.100\n" + " Stream #0:0: Video: wrapped_avframe, bgra, 420x236 [SAR 63:64 DAR 6615:3776], q=2-31, 200 kb/s, 6 fps, 6 tbn, 6 tbc\n" + " Metadata:\n" + " encoder : Lavc58.18.100 wrapped_avframe\n" + "frame= 61 fps=0.0 q=-0.0 Lsize=N/A time=00:00:10.16 bitrate=N/A speed= 219x \n" + "video:32kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown"]; + + MEDIA_INFORMATION_H264 = [NSString stringWithFormat: + @"Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'transition_rotate.mp4':\n" + " Metadata:\n" + " major_brand : isom\n" + " minor_version : 512\n" + " compatible_brands: isomiso2avc1mp41\n" + " encoder : Lavf58.12.100\n" + " Duration: 00:00:15.00, start: 0.000000, bitrate: 7764 kb/s\n" + " Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 7762 kb/s, 25 fps, 30 tbr, 15360 tbn, 60 tbc (default)\n" + " Metadata:\n" + " handler_name : VideoHandler\n" + "Stream mapping:\n" + " Stream #0:0 -> #0:0 (h264 (native) -> wrapped_avframe (native))\n" + "Press [q] to stop, [?] for help\n" + "Output #0, null, to 'pipe:':\n" + " Metadata:\n" + " major_brand : isom\n" + " minor_version : 512\n" + " compatible_brands: isomiso2avc1mp41\n" + " encoder : Lavf58.12.100\n" + " Stream #0:0(und): Video: wrapped_avframe, yuv420p, 1280x720 [SAR 1:1 DAR 16:9], q=2-31, 200 kb/s, 30 fps, 30 tbn, 30 tbc (default)\n" + " Metadata:\n" + " handler_name : VideoHandler\n" + " encoder : Lavc58.18.100 wrapped_avframe\n" + "frame= 375 fps=0.0 q=-0.0 Lsize=N/A time=00:00:15.00 bitrate=N/A speed=35.9x \n" + "video:196kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown"]; + + MEDIA_INFORMATION_PNG = [NSString stringWithFormat: + @"Input #0, png_pipe, from 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png':\n" + " Duration: N/A, bitrate: N/A\n" + " Stream #0:0: Video: png, rgba(pc), 544x184, 25 tbr, 25 tbn, 25 tbc\n" + "Stream mapping:\n" + " Stream #0:0 -> #0:0 (png (native) -> wrapped_avframe (native))\n" + "Press [q] to stop, [?] for help\n" + "Output #0, null, to 'pipe:':\n" + " Metadata:\n" + " encoder : Lavf58.12.100\n" + " Stream #0:0: Video: wrapped_avframe, rgba, 544x184, q=2-31, 200 kb/s, 25 fps, 25 tbn, 25 tbc\n" + " Metadata:\n" + " encoder : Lavc58.18.100 wrapped_avframe\n" + "frame= 1 fps=0.0 q=-0.0 Lsize=N/A time=00:00:00.04 bitrate=N/A speed=27.1x \n" + "video:1kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown"]; + + MEDIA_INFORMATION_H264_2 = [NSString stringWithFormat: + @"Input #0, h264, from 'test.h264':\n" + " Duration: N/A, bitrate: N/A\n" + " Stream #0:0: Video: h264 (Main), yuv420p(tv, bt709, progressive), 1920x1080, 25 fps, 25 tbr, 1200k tbn, 50 tbc\n" + "Stream mapping:\n" + " Stream #0:0 -> #0:0 (h264 (native) -> wrapped_avframe (native))\n" + "Press [q] to stop, [?] for help\n" + "Output #0, null, to 'pipe:':\n" + " Metadata:\n" + " encoder : Lavf58.12.100\n" + " Stream #0:0: Video: wrapped_avframe, yuv420p, 1920x1080, q=2-31, 200 kb/s, 25 fps, 25 tbn, 25 tbc\n" + " Metadata:\n" + " encoder : Lavc58.18.100 wrapped_avframe\n" + "frame= 360 fps=0.0 q=-0.0 Lsize=N/A time=00:00:14.40 bitrate=N/A speed=25.5x \n" + "video:188kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown"]; + + MEDIA_INFORMATION_MP4 = [NSString stringWithFormat: + @"Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_2160p_30fps_stereo_abl.mp4':\n" + " Metadata:\n" + " major_brand : isom\n" + " minor_version : 1\n" + " compatible_brands: isomavc1\n" + " creation_time : 2013-12-16T17:21:55.000000Z\n" + " title : Big Buck Bunny, Sunflower version\n" + " artist : Blender Foundation 2008, Janus Bager Kristensen 2013\n" + " comment : Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net\n" + " genre : Animation\n" + " composer : Sacha Goedegebure\n" + " Duration: 00:10:34.53, start: 0.000000, bitrate: 10385 kb/s\n" + " Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 3840x4320 [SAR 1:1 DAR 8:9], 9902 kb/s, 30 fps, 30 tbr, 30k tbn, 60 tbc (default)\n" + " Metadata:\n" + " creation_time : 2013-12-16T17:21:55.000000Z\n" + " handler_name : GPAC ISO Video Handler\n" + " Stream #0:1(und): Audio: mp3 (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 160 kb/s (default)\n" + " Metadata:\n" + " creation_time : 2013-12-16T17:21:58.000000Z\n" + " handler_name : GPAC ISO Audio Handler\n" + " Stream #0:2(und): Audio: ac3 (ac-3 / 0x332D6361), 48000 Hz, 5.1(side), fltp, 320 kb/s (default)\n" + " Metadata:\n" + " creation_time : 2013-12-16T17:21:58.000000Z\n" + " handler_name : GPAC ISO Audio Handler\n" + " Side data:\n" + " audio service type: main\n" + "Stream mapping:\n" + " Stream #0:0 -> #0:0 (h264 (native) -> wrapped_avframe (native))\n" + " Stream #0:2 -> #0:1 (ac3 (native) -> pcm_s16le (native))\n" + "Press [q] to stop, [?] for help\n" + "Output #0, null, to 'pipe:':\n" + " Metadata:\n" + " major_brand : isom\n" + " minor_version : 1\n" + " compatible_brands: isomavc1\n" + " composer : Sacha Goedegebure\n" + " title : Big Buck Bunny, Sunflower version\n" + " artist : Blender Foundation 2008, Janus Bager Kristensen 2013\n" + " comment : Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net\n" + " genre : Animation\n" + " encoder : Lavf58.12.100\n" + " Stream #0:0(und): Video: wrapped_avframe, yuv420p(progressive), 3840x4320 [SAR 1:1 DAR 8:9], q=2-31, 200 kb/s, 30 fps, 30 tbn, 30 tbc (default)\n" + " Metadata:\n" + " creation_time : 2013-12-16T17:21:55.000000Z\n" + " handler_name : GPAC ISO Video Handler\n" + " encoder : Lavc58.18.100 wrapped_avframe\n" + " Stream #0:1(und): Audio: pcm_s16le, 48000 Hz, 5.1(side), s16, 4608 kb/s (default)\n" + " Metadata:\n" + " creation_time : 2013-12-16T17:21:58.000000Z\n" + " handler_name : GPAC ISO Audio Handler\n" + " encoder : Lavc58.18.100 pcm_s16le\n" + " Side data:\n" + " audio service type: main\n" + "frame= 2798 fps= 85 q=-0.0 size=N/A time=00:01:33.33 bitrate=N/A speed=2.85x \n"]; + + MEDIA_INFORMATION_MP4_2 = [NSString stringWithFormat: + @"Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_30fps_stereo_arcd.mp4':\n" + " Metadata:\n" + " major_brand : isom\n" + " minor_version : 1\n" + " compatible_brands: isomavc1\n" + " creation_time : 2013-12-16T17:49:59.000000Z\n" + " title : Big Buck Bunny, Sunflower version\n" + " artist : Blender Foundation 2008, Janus Bager Kristensen 2013\n" + " comment : Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net\n" + " genre : Animation\n" + " composer : Sacha Goedegebure\n" + " Duration: 00:10:34.53, start: 0.000000, bitrate: 4474 kb/s\n" + " Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 3992 kb/s, 30 fps, 30 tbr, 30k tbn, 60 tbc (default)\n" + " Metadata:\n" + " creation_time : 2013-12-16T17:49:59.000000Z\n" + " handler_name : GPAC ISO Video Handler\n" + " Stream #0:1(und): Audio: mp3 (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 160 kb/s (default)\n" + " Metadata:\n" + " creation_time : 2013-12-16T17:50:04.000000Z\n" + " handler_name : GPAC ISO Audio Handler\n" + " Stream #0:2(und): Audio: ac3 (ac-3 / 0x332D6361), 48000 Hz, 5.1(side), fltp, 320 kb/s (default)\n" + " Metadata:\n" + " creation_time : 2013-12-16T17:50:04.000000Z\n" + " handler_name : GPAC ISO Audio Handler\n" + " Side data:\n" + " audio service type: main\n" + "Stream mapping:\n" + " Stream #0:0 -> #0:0 (h264 (native) -> wrapped_avframe (native))\n" + " Stream #0:2 -> #0:1 (ac3 (native) -> pcm_s16le (native))\n" + "Press [q] to stop, [?] for help\n" + "Output #0, null, to 'pipe:':\n" + " Metadata:\n" + " major_brand : isom\n" + " minor_version : 1\n" + " compatible_brands: isomavc1\n" + " composer : Sacha Goedegebure\n" + " title : Big Buck Bunny, Sunflower version\n" + " artist : Blender Foundation 2008, Janus Bager Kristensen 2013\n" + " comment : Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net\n" + " genre : Animation\n" + " encoder : Lavf58.12.100\n" + " Stream #0:0(und): Video: wrapped_avframe, yuv420p(progressive), 1920x1080 [SAR 1:1 DAR 16:9], q=2-31, 200 kb/s, 30 fps, 30 tbn, 30 tbc (default)\n" + " Metadata:\n" + " creation_time : 2013-12-16T17:49:59.000000Z\n" + " handler_name : GPAC ISO Video Handler\n" + " encoder : Lavc58.18.100 wrapped_avframe\n" + " Stream #0:1(und): Audio: pcm_s16le, 48000 Hz, 5.1(side), s16, 4608 kb/s (default)\n" + " Metadata:\n" + " creation_time : 2013-12-16T17:50:04.000000Z\n" + " handler_name : GPAC ISO Audio Handler\n" + " encoder : Lavc58.18.100 pcm_s16le\n" + " Side data:\n" + " audio service type: main\n" + "frame=19036 fps=401 q=-0.0 Lsize=N/A time=00:10:34.60 bitrate=N/A speed=13.4x \n" + "video:9964kB audio:356706kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown"]; + + MEDIA_INFORMATION_OGG = [NSString stringWithFormat: + @"[theora @ 0x7fa30c026e00] 7 bits left in packet 82\n" + "[ogg @ 0x7fa30c005e00] Broken file, keyframe not correctly marked.\n" + "Input #0, ogg, from 'trailer_400p.ogg':\n" + " Duration: 00:00:33.00, start: 0.000000, bitrate: 1057 kb/s\n" + " Stream #0:0: Video: theora, yuv420p(bt470bg/bt470bg/bt709), 720x400, 25 fps, 25 tbr, 25 tbn, 25 tbc\n" + " Metadata:\n" + " ENCODER : ffmpeg2theora 0.19\n" + " Stream #0:1: Audio: vorbis, 48000 Hz, stereo, fltp, 80 kb/s\n" + " Metadata:\n" + " ENCODER : ffmpeg2theora 0.19\n" + "[theora @ 0x7fa30c1bd600] 7 bits left in packet 82\n" + "Stream mapping:\n" + " Stream #0:0 -> #0:0 (theora (native) -> wrapped_avframe (native))\n" + " Stream #0:1 -> #0:1 (vorbis (native) -> pcm_s16le (native))\n" + "Press [q] to stop, [?] for help\n" + "Output #0, null, to 'pipe:':\n" + " Metadata:\n" + " encoder : Lavf58.12.100\n" + " Stream #0:0: Video: wrapped_avframe, yuv420p(progressive), 720x400, q=2-31, 200 kb/s, 25 fps, 25 tbn, 25 tbc\n" + " Metadata:\n" + " encoder : Lavc58.18.100 wrapped_avframe\n" + " Stream #0:1: Audio: pcm_s16le, 48000 Hz, stereo, s16, 1536 kb/s\n" + " Metadata:\n" + " encoder : Lavc58.18.100 pcm_s16le\n" + "[ogg @ 0x7fa30c005e00] Broken file, keyframe not correctly marked.\n" + " Last message repeated 5 times\n" + "frame= 813 fps=0.0 q=-0.0 Lsize=N/A time=00:00:33.01 bitrate=N/A speed= 234x \n" + "video:426kB audio:6190kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown"]; +} + +void assertNumber(NSNumber *expected, NSNumber *real) { + if (expected == nil) { + assert(real == nil); + } else { + assert([expected isEqualToNumber:real]); + } +} + +void assertString(NSString *expected, NSString *real) { + if (expected == nil) { + assert(real == nil); + } else { + assert([expected isEqualToString:real]); + } +} + +void assertVideoStream(StreamInformation *stream, NSNumber *index, NSString *codec, NSString *fullCodec, NSString *format, NSString *fullFormat, NSNumber *width, NSNumber *height, NSString *sampleAspectRatio, NSString *displayAspectRatio, NSNumber *bitrate, NSString *averageFrameRate, NSString *realFrameRate, NSString *timeBase, NSString *codecTimeBase) { + + assert(stream != nil); + assertNumber(index, [stream getIndex]); + assertString(@"video", [stream getType]); + assertString(codec, [stream getCodec]); + assertString(fullCodec, [stream getFullCodec]); + + assertString(format, [stream getFormat]); + assertString(fullFormat, [stream getFullFormat]); + + assertNumber(width, [stream getWidth]); + assertNumber(height, [stream getHeight]); + assertString(sampleAspectRatio, [stream getSampleAspectRatio]); + assertString(displayAspectRatio, [stream getDisplayAspectRatio]); + + assertNumber(bitrate, [stream getBitrate]); + assertString(averageFrameRate, [stream getAverageFrameRate]); + assertString(realFrameRate, [stream getRealFrameRate]); + assertString(timeBase, [stream getTimeBase]); + assertString(codecTimeBase, [stream getCodecTimeBase]); +} + +void parseVideoStreamBlock(NSString *input, NSNumber *index, NSString *codec, NSString *fullCodec, NSString *format, NSString *fullFormat, NSNumber *width, NSNumber *height, NSString *sampleAspectRatio, NSString *displayAspectRatio, NSNumber *bitrate, NSString *averageFrameRate, NSString *realFrameRate, NSString *timeBase, NSString *codecTimeBase) { + StreamInformation *stream = [MediaInformationParser parseStreamBlock:input]; + + assertVideoStream(stream, index, codec, fullCodec, format, fullFormat, width, height, sampleAspectRatio, displayAspectRatio, bitrate, averageFrameRate, realFrameRate, timeBase, codecTimeBase); +} + +void assertAudioStream(StreamInformation *stream, NSNumber *index, NSString *codec, NSString *fullCodec, NSNumber *sampleRate, NSString *channelLayout, NSString *sampleFormat, NSNumber *bitrate) { + + assert(stream != nil); + assertNumber(index, [stream getIndex]); + assertString(@"audio", [stream getType]); + assertString(codec, [stream getCodec]); + assertString(fullCodec, [stream getFullCodec]); + assertNumber(sampleRate, [stream getSampleRate]); + assertString(channelLayout, [stream getChannelLayout]); + assertString(sampleFormat, [stream getSampleFormat]); + assertNumber(bitrate, [stream getBitrate]); +} + +void parseAudioStreamBlock(NSString *input, NSNumber *index, NSString *codec, NSString *fullCodec, NSNumber *sampleRate, NSString *channelLayout, NSString *sampleFormat, NSNumber *bitrate) { + StreamInformation *stream = [MediaInformationParser parseStreamBlock:input]; + + assertAudioStream(stream, index, codec, fullCodec, sampleRate, channelLayout, sampleFormat, bitrate); +} + +void parseVideoDimensions(NSString *input, NSNumber *expectedWidth, NSNumber *expectedHeight) { + void(^pair)(NSNumber **, NSNumber **) = [MediaInformationParser parseVideoDimensions:input]; + + NSNumber *width; + NSNumber *height; + pair(&width, &height); + + assert(pair != nil); + if (width == nil) { + assert(expectedWidth == nil); + } else { + assert([width isEqualToNumber:expectedWidth]); + } + if (height == nil) { + assert(expectedHeight == nil); + } else { + assert([height isEqualToNumber:expectedHeight]); + } +} + +void parseDuration(NSString *input, NSNumber *expectedDuration) { + NSNumber *duration = [MediaInformationParser parseDuration:input]; + + if (duration == nil) { + assert(expectedDuration == nil); + } else { + assert([duration isEqualToNumber:expectedDuration]); + } +} + +void parseStartTime(NSString *input, NSNumber *expectedStartTime) { + NSNumber *startTime = [MediaInformationParser parseStartTime:input]; + + if (startTime == nil) { + assert(expectedStartTime == nil); + } else { + assert([startTime isEqualToNumber:expectedStartTime]); + } +} + +void parseDurationBlock(NSString *input, NSNumber *expectedDuration, NSNumber *expectedStartTime, NSNumber *expectedBitrate) { + void(^trio)(NSNumber **, NSNumber **, NSNumber **) = [MediaInformationParser parseDurationBlock:input]; + + NSNumber *duration; + NSNumber *startTime; + NSNumber *bitrate; + trio(&duration, &startTime, &bitrate); + + assert(trio != nil); + if (duration == nil) { + assert(expectedDuration == nil); + } else { + assert([duration isEqualToNumber:expectedDuration]); + } + if (startTime == nil) { + assert(expectedStartTime == nil); + } else { + assert([startTime isEqualToNumber:expectedStartTime]); + } + if (bitrate == nil) { + assert(expectedBitrate == nil); + } else { + assert([bitrate isEqualToNumber:expectedBitrate]); + } +} + +void parseMetadataBlock(NSString *input, NSString *expectedKey, NSString *expectedValue) { + void(^pair)(NSString **, NSString **) = [MediaInformationParser parseMetadataBlock:input]; + + NSString *key; + NSString *value; + pair(&key, &value); + + assert(pair != nil); + assert(key != nil); + assert(value != nil); + assert([key isEqualToString:expectedKey]); + assert([value isEqualToString:expectedValue]); +} + +void parseInputBlock(NSString *input, NSString *expectedFormat, NSString *expectedPath) { + void(^pair)(NSString **, NSString **) = [MediaInformationParser parseInputBlock:input]; + + NSString *format; + NSString *path; + pair(&format, &path); + + assert(pair != nil); + if (format == nil) { + assert(expectedFormat == nil); + } else { + assert([format isEqualToString:expectedFormat]); + } + if (path == nil) { + assert(expectedPath == nil); + } else { + assert([path isEqualToString:expectedPath]); + } +} + +void testParseVideoStream() { + parseVideoStreamBlock(@" Stream #0:0: Video: mjpeg, yuvj420p(pc, bt470bg/unknown/unknown), 2560x1708 [SAR 1:1 DAR 640:427], 25 tbr, 25 tbn, 25 tbc", [[NSNumber alloc] initWithInt:0], @"mjpeg", @"mjpeg", @"yuvj420p", @"yuvj420p(pc, bt470bg/unknown/unknown)", [[NSNumber alloc] initWithInt:2560], [[NSNumber alloc] initWithInt:1708], @"1:1", @"640:427", nil, nil, @"25", @"25", @"25"); + parseVideoStreamBlock(@" Stream #0:0: Video: gif, bgra, 420x236, 6 fps, 6 tbr, 100 tbn, 100 tbc", [[NSNumber alloc] initWithInt:0], @"gif", @"gif", @"bgra", @"bgra", [[NSNumber alloc] initWithInt:420], [[NSNumber alloc] initWithInt:236], nil, nil, nil, @"6", @"6", @"100", @"100"); + parseVideoStreamBlock(@" Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 7762 kb/s, 25 fps, 30 tbr, 15360 tbn, 60 tbc (default)", [[NSNumber alloc] initWithInt:0], @"h264", @"h264 (main) (avc1 / 0x31637661)", @"yuv420p", @"yuv420p", [[NSNumber alloc] initWithInt:1280], [[NSNumber alloc] initWithInt:720], @"1:1", @"16:9", [[NSNumber alloc] initWithInt:7762], @"25", @"30", @"15360", @"60"); + parseVideoStreamBlock(@" Stream #0:0: Video: png, rgba(pc), 544x184, 25 tbr, 25 tbn, 25 tbc", [[NSNumber alloc] initWithInt:0], @"png", @"png", @"rgba", @"rgba(pc)", [[NSNumber alloc] initWithInt:544], [[NSNumber alloc] initWithInt:184], nil, nil, nil, nil, @"25", @"25", @"25"); + parseVideoStreamBlock(@" Stream #0:0: Video: h264 (Main), yuv420p(tv, bt709, progressive), 1920x1080, 25 fps, 25 tbr, 1200k tbn, 50 tbc", [[NSNumber alloc] initWithInt:0], @"h264", @"h264 (main)", @"yuv420p", @"yuv420p(tv, bt709, progressive)", [[NSNumber alloc] initWithInt:1920], [[NSNumber alloc] initWithInt:1080], nil, nil, nil, @"25", @"25", @"1200k", @"50"); + parseVideoStreamBlock(@" Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 3840x4320 [SAR 1:1 DAR 8:9], 9902 kb/s, 30 fps, 30 tbr, 30k tbn, 60 tbc (default)", [[NSNumber alloc] initWithInt:0], @"h264", @"h264 (high) (avc1 / 0x31637661)", @"yuv420p", @"yuv420p", [[NSNumber alloc] initWithInt:3840], [[NSNumber alloc] initWithInt:4320], @"1:1", @"8:9", [[NSNumber alloc] initWithInt:9902], @"30", @"30", @"30k", @"60"); + parseVideoStreamBlock(@" Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 3992 kb/s, 30 fps, 30 tbr, 30k tbn, 60 tbc (default)", [[NSNumber alloc] initWithInt:0], @"h264", @"h264 (high) (avc1 / 0x31637661)", @"yuv420p", @"yuv420p", [[NSNumber alloc] initWithInt:1920], [[NSNumber alloc] initWithInt:1080], @"1:1", @"16:9", [[NSNumber alloc] initWithInt:3992], @"30", @"30", @"30k", @"60"); + parseVideoStreamBlock(@" Stream #0:0: Video: theora, yuv420p(bt470bg/bt470bg/bt709), 720x400, 25 fps, 25 tbr, 25 tbn, 25 tbc", [[NSNumber alloc] initWithInt:0], @"theora", @"theora", @"yuv420p", @"yuv420p(bt470bg/bt470bg/bt709)", [[NSNumber alloc] initWithInt:720], [[NSNumber alloc] initWithInt:400], nil, nil, nil, @"25", @"25", @"25", @"25"); +} + +void testParseAudioStream() { + parseAudioStreamBlock(@"Stream #0:0: Audio: adpcm_ms ([2][0][0][0] / 0x0002), 22050 Hz, stereo, s16, 176 kb/s", [[NSNumber alloc] initWithInt:0], @"adpcm_ms", @"adpcm_ms ([2][0][0][0] / 0x0002)", [[NSNumber alloc] initWithInt:22050], @"stereo", @"s16", [[NSNumber alloc] initWithInt:176]); + parseAudioStreamBlock(@"Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, mono, s16, 705 kb/s",[[NSNumber alloc] initWithInt:0], @"pcm_s16le", @"pcm_s16le ([1][0][0][0] / 0x0001)", [[NSNumber alloc] initWithInt:44100], @"mono", @"s16", [[NSNumber alloc] initWithInt:705]); + parseAudioStreamBlock(@"Stream #0:0: Audio: pcm_s24le ([1][0][0][0] / 0x0001), 48000 Hz, mono, s32 (24 bit), 1152 kb/s", [[NSNumber alloc] initWithInt:0], @"pcm_s24le", @"pcm_s24le ([1][0][0][0] / 0x0001)", [[NSNumber alloc] initWithInt:48000], @"mono", @"s32 (24 bit)", [[NSNumber alloc] initWithInt:1152]); + parseAudioStreamBlock(@"Stream #0:0: Audio: mp3, 48000 Hz, stereo, fltp, 192 kb/s", [[NSNumber alloc] initWithInt:0], @"mp3", @"mp3", [[NSNumber alloc] initWithInt:48000], @"stereo", @"fltp", [[NSNumber alloc] initWithInt:192]); + parseAudioStreamBlock(@"Stream #0:0: Audio: vorbis, 44100 Hz, stereo, fltp, 128 kb/s", [[NSNumber alloc] initWithInt:0], @"vorbis", @"vorbis", [[NSNumber alloc] initWithInt:44100], @"stereo", @"fltp", [[NSNumber alloc] initWithInt:128]); + parseAudioStreamBlock(@"Stream #0:0: Audio: pcm_u8 ([1][0][0][0] / 0x0001), 44100 Hz, stereo, u8, 705 kb/s", [[NSNumber alloc] initWithInt:0], @"pcm_u8", @"pcm_u8 ([1][0][0][0] / 0x0001)", [[NSNumber alloc] initWithInt:44100], @"stereo", @"u8", [[NSNumber alloc] initWithInt:705]); + parseAudioStreamBlock(@"Stream #0:1(und): Audio: mp3 (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 160 kb/s (default)", [[NSNumber alloc] initWithInt:1], @"mp3", @"mp3 (mp4a / 0x6134706d)", [[NSNumber alloc] initWithInt:48000], @"stereo", @"fltp", [[NSNumber alloc] initWithInt:160]); + parseAudioStreamBlock(@"Stream #0:2(und): Audio: ac3 (ac-3 / 0x332D6361), 48000 Hz, 5.1(side), fltp, 320 kb/s (default)", [[NSNumber alloc] initWithInt:2], @"ac3", @"ac3 (ac-3 / 0x332d6361)", [[NSNumber alloc] initWithInt:48000], @"5.1(side)", @"fltp", [[NSNumber alloc] initWithInt:320]); + parseAudioStreamBlock(@"Stream #0:1(und): Audio: mp3 (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 160 kb/s (default)", [[NSNumber alloc] initWithInt:1], @"mp3", @"mp3 (mp4a / 0x6134706d)", [[NSNumber alloc] initWithInt:48000], @"stereo", @"fltp", [[NSNumber alloc] initWithInt:160]); + parseAudioStreamBlock(@"Stream #0:2(und): Audio: ac3 (ac-3 / 0x332D6361), 48000 Hz, 5.1(side), fltp, 320 kb/s (default)", [[NSNumber alloc] initWithInt:2], @"ac3", @"ac3 (ac-3 / 0x332d6361)", [[NSNumber alloc] initWithInt:48000], @"5.1(side)", @"fltp", [[NSNumber alloc] initWithInt:320]); +} + +void testIndex() { + assert(7 == [MediaInformationParser index:@"one:two:three:" of:@":" from:0 times:2]); + assert(13 == [MediaInformationParser index:@"one:two:three:" of:@":" from:0 times:3]); + assert(8 == [MediaInformationParser index:@"one::two::three::" of:@"::" from:0 times:2]); +} + +void testCount() { + assert(3 == [MediaInformationParser count:@"one:two:three:" of:@":"]); + assert(2 == [MediaInformationParser count:@"one,two:three,four" of:@","]); +} + +void testParseVideoStreamDisplayAspectRatio() { + assert(nil == [MediaInformationParser parseVideoStreamDisplayAspectRatio:@""]); + assert(nil == [MediaInformationParser parseVideoStreamDisplayAspectRatio:@"544x184"]); + assert([@"640:427" isEqualToString:[MediaInformationParser parseVideoStreamDisplayAspectRatio:@"2560x1708 [SAR 1:1 DAR 640:427]"]]); + assert([@"8:9" isEqualToString:[MediaInformationParser parseVideoStreamDisplayAspectRatio:@"3840x4320 [SAR 1:1 DAR 8:9]"]]); +} + +void testParseVideoStreamSampleAspectRatio() { + assert(nil == [MediaInformationParser parseVideoStreamSampleAspectRatio:@""]); + assert(nil == [MediaInformationParser parseVideoStreamSampleAspectRatio:@"544x184"]); + assert([@"1:1" isEqualToString:[MediaInformationParser parseVideoStreamSampleAspectRatio:@"2560x1708 [SAR 1:1 DAR 640:427]"]]); + assert([@"1:1" isEqualToString:[MediaInformationParser parseVideoStreamSampleAspectRatio:@"3840x4320 [SAR 1:1 DAR 8:9]"]]); +} + +void testParseVideoDimensions() { + parseVideoDimensions(@"", nil, nil); + parseVideoDimensions(@"544x184", [[NSNumber alloc] initWithInt:544], [[NSNumber alloc] initWithInt:184]); + parseVideoDimensions(@"720x400", [[NSNumber alloc] initWithInt:720], [[NSNumber alloc] initWithInt:400]); + parseVideoDimensions(@"2560x1708 [SAR 1:1 DAR 640:427]", [[NSNumber alloc] initWithInt:2560], [[NSNumber alloc] initWithInt:1708]); + parseVideoDimensions(@"3840x4320 [SAR 1:1 DAR 8:9]", [[NSNumber alloc] initWithInt:3840], [[NSNumber alloc] initWithInt:4320]); +} + +void testParseAudioSampleRate() { + assert([[[NSNumber alloc] initWithInt:44000] isEqualToNumber:[MediaInformationParser parseAudioStreamSampleRate:@"44000"]]); + assert([[[NSNumber alloc] initWithInt:44000] isEqualToNumber:[MediaInformationParser parseAudioStreamSampleRate:@"44 khz"]]); + assert([[[NSNumber alloc] initWithInt:44100] isEqualToNumber:[MediaInformationParser parseAudioStreamSampleRate:@"44100"]]); + assert([[[NSNumber alloc] initWithInt:5000000] isEqualToNumber:[MediaInformationParser parseAudioStreamSampleRate:@"5 mhz"]]); +} + +void testParseAudioStreamType() { + assert([@"video" isEqualToString:[MediaInformationParser parseStreamType:@"Video: theora"]]); + assert([@"video" isEqualToString:[MediaInformationParser parseStreamType:@"Video: png"]]); + assert([@"video" isEqualToString:[MediaInformationParser parseStreamType:@"Video: h264 (Main)"]]); + assert([@"audio" isEqualToString:[MediaInformationParser parseStreamType:@"Audio: adpcm_ms ([2][0][0][0] / 0x0002)"]]); + assert([@"audio" isEqualToString:[MediaInformationParser parseStreamType:@"Audio: mp3 (mp4a / 0x6134706D)"]]); + assert([@"audio" isEqualToString:[MediaInformationParser parseStreamType:@"Audio: pcm_u8 ([1][0][0][0] / 0x0001)"]]); +} + +void testParseStreamCodec() { + assert([@"theora" isEqualToString:[MediaInformationParser parseStreamCodec:@"Video: theora"]]); + assert([@"png" isEqualToString:[MediaInformationParser parseStreamCodec:@"Video: png"]]); + assert([@"h264" isEqualToString:[MediaInformationParser parseStreamCodec:@"Video: h264 (Main)"]]); + assert([@"adpcm_ms" isEqualToString:[MediaInformationParser parseStreamCodec:@"Audio: adpcm_ms ([2][0][0][0] / 0x0002)"]]); + assert([@"mp3" isEqualToString:[MediaInformationParser parseStreamCodec:@"Audio: mp3 (mp4a / 0x6134706D)"]]); + assert([@"pcm_u8" isEqualToString:[MediaInformationParser parseStreamCodec:@"Audio: pcm_u8 ([1][0][0][0] / 0x0001)"]]); +} + +void testParseStreamFullCodec() { + assert([@"theora" isEqualToString:[MediaInformationParser parseStreamFullCodec:@"Video: theora"]]); + assert([@"png" isEqualToString:[MediaInformationParser parseStreamFullCodec:@"Video: png"]]); + assert([@"h264 (main)" isEqualToString:[MediaInformationParser parseStreamFullCodec:@"Video: h264 (Main)"]]); + assert([@"adpcm_ms ([2][0][0][0] / 0x0002)" isEqualToString:[MediaInformationParser parseStreamFullCodec:@"Audio: adpcm_ms ([2][0][0][0] / 0x0002)"]]); + assert([@"mp3 (mp4a / 0x6134706d)" isEqualToString:[MediaInformationParser parseStreamFullCodec:@"Audio: mp3 (mp4a / 0x6134706D)"]]); + assert([@"pcm_u8 ([1][0][0][0] / 0x0001)" isEqualToString:[MediaInformationParser parseStreamFullCodec:@"Audio: pcm_u8 ([1][0][0][0] / 0x0001)"]]); +} + +void testParseStreamIndex() { + assert([[[NSNumber alloc] initWithInt: 0] isEqualToNumber:[MediaInformationParser parseStreamIndex:@"Stream #0:0(und): Audio"]]); + assert([[[NSNumber alloc] initWithInt: 1] isEqualToNumber:[MediaInformationParser parseStreamIndex:@"Stream #0:1(und): Video"]]); + assert([[[NSNumber alloc] initWithInt: 2] isEqualToNumber:[MediaInformationParser parseStreamIndex:@"Stream #0:2(und): Audio"]]); + assert([[[NSNumber alloc] initWithInt: 0] isEqualToNumber:[MediaInformationParser parseStreamIndex:@"Stream #0:0: Video"]]); + assert([[[NSNumber alloc] initWithInt: 1] isEqualToNumber:[MediaInformationParser parseStreamIndex:@"Stream #0:1: Audio"]]); + assert([[[NSNumber alloc] initWithInt: 2] isEqualToNumber:[MediaInformationParser parseStreamIndex:@"Stream #0:2: Video"]]); +} + +void testParseDuration() { + parseDuration(nil, nil); + parseDuration(@"", nil); + parseDuration(@"N/A", nil); + parseDuration(@"00:03:33.24", [[NSNumber alloc] initWithInt: 213240]); + parseDuration(@"00:10:34.53", [[NSNumber alloc] initWithInt: 634530]); + parseDuration(@"00:00:00.04", [[NSNumber alloc] initWithInt: 40]); + parseDuration(@"00:00:15.00", [[NSNumber alloc] initWithInt: 15000]); +} + +void testParseStartTime() { + parseStartTime(nil, nil); + parseStartTime(@"", nil); + parseStartTime(@"N/A", nil); + parseStartTime(@"0.000000", [[NSNumber alloc] initWithInt:0]); + parseStartTime(@"10.003000", [[NSNumber alloc] initWithInt:10003]); + parseStartTime(@"324.000000", [[NSNumber alloc] initWithInt:324000]); + parseStartTime(@"-4.000000", [[NSNumber alloc] initWithInt:-4000]); + parseStartTime(@"14.00030", [[NSNumber alloc] initWithInt:14001]); + parseStartTime(@"14.00080", [[NSNumber alloc] initWithInt:14001]); +} + +void testParseDurationBlock() { + parseDurationBlock(@" Duration: 00:03:33.24, start: 0.000000, bitrate: 320 kb/s", [[NSNumber alloc] initWithInt:213240], [[NSNumber alloc] initWithInt:0], [[NSNumber alloc] initWithInt:320]); + parseDurationBlock(@" Duration: 00:00:00.04, start: 0.000000, bitrate: 391187 kb/s", [[NSNumber alloc] initWithInt:40], [[NSNumber alloc] initWithInt:0], [[NSNumber alloc] initWithInt:391187]); + parseDurationBlock(@" Duration: N/A, bitrate: N/A", nil, nil, nil); + parseDurationBlock(@" Duration: 00:00:15.00, start: 0.000000, bitrate: 7764 kb/s", [[NSNumber alloc] initWithInt:15000], [[NSNumber alloc] initWithInt:0], [[NSNumber alloc] initWithInt:7764]); + parseDurationBlock(@" Duration: 00:10:34.53, start: 0.000000, bitrate: 4474 kb/s", [[NSNumber alloc] initWithInt:634530], [[NSNumber alloc] initWithInt:0], [[NSNumber alloc] initWithInt:4474]); +} + +void testParseInputBlock() { + parseInputBlock(@"Input #0,", nil, nil); + parseInputBlock(@"Input #0, ogg, from 'trailer_400p.ogg':", @"ogg", @"trailer_400p.ogg"); + parseInputBlock(@"Input #0, mp3, from 'beethoven_-_symphony_no_9.mp3':", @"mp3", @"beethoven_-_symphony_no_9.mp3"); + parseInputBlock(@"Input #0, image2, from '/data/user/0/com.arthenica.mobileffmpeg.test/cache/colosseum.jpg':", @"image2", @"/data/user/0/com.arthenica.mobileffmpeg.test/cache/colosseum.jpg"); + parseInputBlock(@"Input #0, gif, from 'advanced_zoom_in_and_pan_with_fade_in_out.gif':", @"gif", @"advanced_zoom_in_and_pan_with_fade_in_out.gif"); + parseInputBlock(@"Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'transition_rotate.mp4':", @"mov,mp4,m4a,3gp,3g2,mj2", @"transition_rotate.mp4"); + parseInputBlock(@"Input #0, png_pipe, from 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png':", @"png_pipe", @"https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"); +} + +void testParseMetadataBlock() { + parseMetadataBlock(@" ENCODER:", @"ENCODER", @""); + parseMetadataBlock(@" ENCODER:ffmpeg2theora 0.19", @"ENCODER", @"ffmpeg2theora 0.19"); + parseMetadataBlock(@" ENCODER : ffmpeg2theora 0.19", @"ENCODER", @"ffmpeg2theora 0.19"); + parseMetadataBlock(@" creation_time : 2013-12-16T17:50:04.000000Z", @"creation_time", @"2013-12-16T17:50:04.000000Z"); + parseMetadataBlock(@" handler_name : GPAC ISO Audio Handler", @"handler_name", @"GPAC ISO Audio Handler"); + parseMetadataBlock(@" comment : Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net", @"comment", @"Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net"); + parseMetadataBlock(@" minor_version : 1", @"minor_version", @"1"); + parseMetadataBlock(@" encoder : Lavf58.12.100", @"encoder", @"Lavf58.12.100"); + parseMetadataBlock(@" title : Planet X", @"title", @"Planet X"); + parseMetadataBlock(@" compatible_brands: isomiso2avc1mp41", @"compatible_brands", @"isomiso2avc1mp41"); +} + +void assertNotNull(NSObject *object) { + assert(object != nil); +} + +void assertMediaInput(MediaInformation *mediaInformation, NSString *expectedFormat, NSString *expectedPath) { + NSString *format = [mediaInformation getFormat]; + NSString *path = [mediaInformation getPath]; + if (format == nil) { + assert(expectedFormat == nil); + } else { + assert([format isEqualToString:expectedFormat]); + } + if (path == nil) { + assert(expectedPath == nil); + } else { + assert([path isEqualToString:expectedPath]); + } +} + +void assertMediaDuration(MediaInformation *mediaInformation, NSNumber *expectedDuration, NSNumber *expectedStartTime, NSNumber *expectedBitrate) { + NSNumber *duration = [mediaInformation getDuration]; + NSNumber *startTime = [mediaInformation getStartTime]; + NSNumber *bitrate = [mediaInformation getBitrate]; + + if (duration == nil) { + assert(expectedDuration == nil); + } else { + assert([duration isEqualToNumber:expectedDuration]); + } + if (startTime == nil) { + assert(expectedStartTime == nil); + } else { + assert([startTime isEqualToNumber:expectedStartTime]); + } + if (bitrate == nil) { + assert(expectedBitrate == nil); + } else { + assert([bitrate isEqualToNumber:expectedBitrate]); + } +} + +void testMediaInformationMp3() { + MediaInformation *mediaInformation = [MediaInformationParser from:MEDIA_INFORMATION_MP3]; + + assertNotNull(mediaInformation); + assertMediaInput(mediaInformation, @"mp3", @"beethoven_-_symphony_no_9.mp3"); + assertMediaDuration(mediaInformation, [[NSNumber alloc] initWithInt: 213240], [[NSNumber alloc] initWithInt: 0], [[NSNumber alloc] initWithInt: 320]); + + NSArray *streams = [mediaInformation getStreams]; + assertNotNull(streams); + assert(1 == [streams count]); + + assertAudioStream([streams objectAtIndex:0], [[NSNumber alloc] initWithInt:0], @"mp3", @"mp3", [[NSNumber alloc] initWithInt:48000], @"stereo", @"fltp", [[NSNumber alloc] initWithInt:320]); +} + +void testMediaInformationJpg() { + MediaInformation *mediaInformation = [MediaInformationParser from:MEDIA_INFORMATION_JPG]; + + assertNotNull(mediaInformation); + assertMediaInput(mediaInformation, @"image2", @"/data/user/0/com.arthenica.mobileffmpeg.test/cache/colosseum.jpg"); + assertMediaDuration(mediaInformation, [[NSNumber alloc] initWithInt:40], [[NSNumber alloc] initWithInt:0], [[NSNumber alloc] initWithInt:391187]); + + NSArray *streams = [mediaInformation getStreams]; + assertNotNull(streams); + assert(1 == [streams count]); + + assertVideoStream([streams objectAtIndex:0], [[NSNumber alloc] initWithInt:0], @"mjpeg", @"mjpeg", @"yuvj420p", @"yuvj420p(pc, bt470bg/unknown/unknown)", [[NSNumber alloc] initWithInt:2560], [[NSNumber alloc] initWithInt:1708], @"1:1", @"640:427", nil, nil, @"25", @"25", @"25"); +} + +void testMediaInformationGif() { + MediaInformation *mediaInformation = [MediaInformationParser from:MEDIA_INFORMATION_GIF]; + + assertNotNull(mediaInformation); + assertMediaInput(mediaInformation, @"gif", @"advanced_zoom_in_and_pan_with_fade_in_out.gif"); + assertMediaDuration(mediaInformation, nil, nil, nil); + + NSArray *streams = [mediaInformation getStreams]; + assertNotNull(streams); + assert(1 == [streams count]); + + assertVideoStream([streams objectAtIndex:0], [[NSNumber alloc] initWithInt:0], @"gif", @"gif", @"bgra", @"bgra", [[NSNumber alloc] initWithInt:420], [[NSNumber alloc] initWithInt:236], nil, nil, nil, @"6", @"6", @"100", @"100"); +} + +void testMediaInformationH264() { + MediaInformation *mediaInformation = [MediaInformationParser from:MEDIA_INFORMATION_H264]; + + assertNotNull(mediaInformation); + assertMediaInput(mediaInformation, @"mov,mp4,m4a,3gp,3g2,mj2", @"transition_rotate.mp4"); + assertMediaDuration(mediaInformation, [[NSNumber alloc] initWithInt:15000], [[NSNumber alloc] initWithInt:0], [[NSNumber alloc] initWithInt:7764]); + + NSArray *streams = [mediaInformation getStreams]; + assertNotNull(streams); + assert(1 == [streams count]); + + assertVideoStream([streams objectAtIndex:0], [[NSNumber alloc] initWithInt:0], @"h264", @"h264 (main) (avc1 / 0x31637661)", @"yuv420p", @"yuv420p", [[NSNumber alloc] initWithInt:1280], [[NSNumber alloc] initWithInt:720], @"1:1", @"16:9", [[NSNumber alloc] initWithInt:7762], @"25", @"30", @"15360", @"60"); +} + +void testMediaInformationPng() { + MediaInformation *mediaInformation = [MediaInformationParser from:MEDIA_INFORMATION_PNG]; + + assertNotNull(mediaInformation); + assertMediaInput(mediaInformation, @"png_pipe", @"https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"); + assertMediaDuration(mediaInformation, nil, nil, nil); + + NSArray *streams = [mediaInformation getStreams]; + assertNotNull(streams); + assert(1 == [streams count]); + + assertVideoStream([streams objectAtIndex:0], [[NSNumber alloc] initWithInt:0], @"png", @"png", @"rgba", @"rgba(pc)", [[NSNumber alloc] initWithInt:544], [[NSNumber alloc] initWithInt:184], nil, nil, nil, nil, @"25", @"25", @"25"); +} + +void testMediaInformationH2642() { + MediaInformation *mediaInformation = [MediaInformationParser from:MEDIA_INFORMATION_H264_2]; + + assertNotNull(mediaInformation); + assertMediaInput(mediaInformation, @"h264", @"test.h264"); + assertMediaDuration(mediaInformation, nil, nil, nil); + + NSArray *streams = [mediaInformation getStreams]; + assertNotNull(streams); + assert(1 == [streams count]); + + assertVideoStream([streams objectAtIndex:0], [[NSNumber alloc] initWithInt:0], @"h264", @"h264 (main)", @"yuv420p", @"yuv420p(tv, bt709, progressive)", [[NSNumber alloc] initWithInt:1920], [[NSNumber alloc] initWithInt:1080], nil, nil, nil, @"25", @"25", @"1200k", @"50"); +} + +void testMediaInformationMp4() { + MediaInformation *mediaInformation = [MediaInformationParser from:MEDIA_INFORMATION_MP4]; + + assertNotNull(mediaInformation); + assertMediaInput(mediaInformation, @"mov,mp4,m4a,3gp,3g2,mj2", @"http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_2160p_30fps_stereo_abl.mp4"); + assertMediaDuration(mediaInformation, [[NSNumber alloc] initWithInt:634530], [[NSNumber alloc] initWithInt:0], [[NSNumber alloc] initWithInt:10385]); + + NSArray *streams = [mediaInformation getStreams]; + assertNotNull(streams); + assert(3 == [streams count]); + + assertVideoStream([streams objectAtIndex:0], [[NSNumber alloc] initWithInt:0], @"h264", @"h264 (high) (avc1 / 0x31637661)", @"yuv420p", @"yuv420p", [[NSNumber alloc] initWithInt:3840], [[NSNumber alloc] initWithInt:4320], @"1:1", @"8:9", [[NSNumber alloc] initWithInt:9902], @"30", @"30", @"30k", @"60"); + assertAudioStream([streams objectAtIndex:1], [[NSNumber alloc] initWithInt:1], @"mp3", @"mp3 (mp4a / 0x6134706d)", [[NSNumber alloc] initWithInt:48000], @"stereo", @"fltp", [[NSNumber alloc] initWithInt:160]); + assertAudioStream([streams objectAtIndex:2], [[NSNumber alloc] initWithInt:2], @"ac3", @"ac3 (ac-3 / 0x332d6361)", [[NSNumber alloc] initWithInt:48000], @"5.1(side)", @"fltp", [[NSNumber alloc] initWithInt:320]); +} + +void testMediaInformationMp42() { + MediaInformation *mediaInformation = [MediaInformationParser from:MEDIA_INFORMATION_MP4_2]; + + assertNotNull(mediaInformation); + assertMediaInput(mediaInformation, @"mov,mp4,m4a,3gp,3g2,mj2", @"http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_30fps_stereo_arcd.mp4"); + assertMediaDuration(mediaInformation, [[NSNumber alloc] initWithInt:634530], [[NSNumber alloc] initWithInt:0], [[NSNumber alloc] initWithInt:4474]); + + NSArray *streams = [mediaInformation getStreams]; + assertNotNull(streams); + assert(3 == [streams count]); + + assertVideoStream([streams objectAtIndex:0], [[NSNumber alloc] initWithInt:0], @"h264", @"h264 (high) (avc1 / 0x31637661)", @"yuv420p", @"yuv420p", [[NSNumber alloc] initWithInt:1920], [[NSNumber alloc] initWithInt:1080], @"1:1", @"16:9", [[NSNumber alloc] initWithInt:3992], @"30", @"30", @"30k", @"60"); + assertAudioStream([streams objectAtIndex:1], [[NSNumber alloc] initWithInt:1], @"mp3", @"mp3 (mp4a / 0x6134706d)", [[NSNumber alloc] initWithInt:48000], @"stereo", @"fltp", [[NSNumber alloc] initWithInt:160]); + assertAudioStream([streams objectAtIndex:2], [[NSNumber alloc] initWithInt:2], @"ac3", @"ac3 (ac-3 / 0x332d6361)", [[NSNumber alloc] initWithInt:48000], @"5.1(side)", @"fltp", [[NSNumber alloc] initWithInt:320]); +} + +void testMediaInformationOgg() { + MediaInformation *mediaInformation = [MediaInformationParser from:MEDIA_INFORMATION_OGG]; + + assertNotNull(mediaInformation); + assertMediaInput(mediaInformation, @"ogg", @"trailer_400p.ogg"); + assertMediaDuration(mediaInformation, [[NSNumber alloc] initWithInt:33000], [[NSNumber alloc] initWithInt:0], [[NSNumber alloc] initWithInt:1057]); + + NSArray *streams = [mediaInformation getStreams]; + assertNotNull(streams); + assert(2 == [streams count]); + + assertVideoStream([streams objectAtIndex:0], [[NSNumber alloc] initWithInt:0], @"theora", @"theora", @"yuv420p", @"yuv420p(bt470bg/bt470bg/bt709)", [[NSNumber alloc] initWithInt:720], [[NSNumber alloc] initWithInt:400], nil, nil, nil, @"25", @"25", @"25", @"25"); + assertAudioStream([streams objectAtIndex:1], [[NSNumber alloc] initWithInt:1], @"vorbis", @"vorbis", [[NSNumber alloc] initWithInt:48000], @"stereo", @"fltp", [[NSNumber alloc] initWithInt:80]); +} + +/** + * All parsing tests are initiated from this method + */ +void runMediaInformationParserTests() { + + initTests(); + + testParseMetadataBlock(); + testParseInputBlock(); + + testParseDurationBlock(); + testParseStartTime(); + testParseDuration(); + + testParseStreamIndex(); + testParseStreamFullCodec(); + testParseStreamCodec(); + + testParseAudioStreamType(); + testParseAudioSampleRate(); + testParseVideoDimensions(); + + testParseVideoStreamSampleAspectRatio(); + testParseVideoStreamDisplayAspectRatio(); + + testCount(); + testIndex(); + + testParseAudioStream(); + testParseVideoStream(); + + testMediaInformationMp3(); + testMediaInformationJpg(); + testMediaInformationGif(); + testMediaInformationH264(); + testMediaInformationPng(); + testMediaInformationH2642(); + testMediaInformationMp4(); + testMediaInformationMp42(); + testMediaInformationOgg(); + + + NSLog(@"MediaInformationParserTests passed."); +} diff --git a/ios/test-app/MobileFFmpegTest/main.m b/ios/test-app/MobileFFmpegTest/main.m index 23308268c..eaaa0ada6 100644 --- a/ios/test-app/MobileFFmpegTest/main.m +++ b/ios/test-app/MobileFFmpegTest/main.m @@ -20,10 +20,16 @@ // #import + #import "AppDelegate.h" +#import "MediaInformationParserTests.h" int main(int argc, char * argv[]) { @autoreleasepool { + + // RUN UNIT TESTS BEFORE STARTING THE APPLICATION + runMediaInformationParserTests(); + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }