Ver Código Fonte

First commit.

felipe 7 anos atrás
commit
f91652b869

+ 41 - 0
.classpath

@@ -0,0 +1,41 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<classpath>
3
+	<classpathentry kind="src" output="target/classes" path="src/main/java">
4
+		<attributes>
5
+			<attribute name="optional" value="true"/>
6
+			<attribute name="maven.pomderived" value="true"/>
7
+		</attributes>
8
+	</classpathentry>
9
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
10
+		<attributes>
11
+			<attribute name="maven.pomderived" value="true"/>
12
+		</attributes>
13
+	</classpathentry>
14
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java">
15
+		<attributes>
16
+			<attribute name="optional" value="true"/>
17
+			<attribute name="maven.pomderived" value="true"/>
18
+		</attributes>
19
+	</classpathentry>
20
+	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
21
+		<attributes>
22
+			<attribute name="maven.pomderived" value="true"/>
23
+		</attributes>
24
+	</classpathentry>
25
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
26
+		<attributes>
27
+			<attribute name="maven.pomderived" value="true"/>
28
+		</attributes>
29
+	</classpathentry>
30
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
31
+		<attributes>
32
+			<attribute name="maven.pomderived" value="true"/>
33
+		</attributes>
34
+	</classpathentry>
35
+	<classpathentry combineaccessrules="false" kind="src" path="/br.com.innovox.apps"/>
36
+	<classpathentry kind="lib" path="lib/Cortix7_fingerprinter.jar"/>
37
+	<classpathentry kind="lib" path="lib/Cortix7_sharedEntities.jar"/>
38
+	<classpathentry combineaccessrules="false" kind="src" path="/br.com.innovox.lib.javautils"/>
39
+	<classpathentry combineaccessrules="false" kind="src" path="/br.com.innovox.lib.soundmatch"/>
40
+	<classpathentry kind="output" path="target/classes"/>
41
+</classpath>

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
1
+/target/

+ 23 - 0
.project

@@ -0,0 +1,23 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<projectDescription>
3
+	<name>BKE_SVSpotDiscoverer</name>
4
+	<comment></comment>
5
+	<projects>
6
+	</projects>
7
+	<buildSpec>
8
+		<buildCommand>
9
+			<name>org.eclipse.jdt.core.javabuilder</name>
10
+			<arguments>
11
+			</arguments>
12
+		</buildCommand>
13
+		<buildCommand>
14
+			<name>org.eclipse.m2e.core.maven2Builder</name>
15
+			<arguments>
16
+			</arguments>
17
+		</buildCommand>
18
+	</buildSpec>
19
+	<natures>
20
+		<nature>org.eclipse.jdt.core.javanature</nature>
21
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
22
+	</natures>
23
+</projectDescription>

+ 8 - 0
.settings/org.eclipse.jdt.core.prefs

@@ -0,0 +1,8 @@
1
+eclipse.preferences.version=1
2
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
3
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
4
+org.eclipse.jdt.core.compiler.compliance=1.8
5
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
6
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
7
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
8
+org.eclipse.jdt.core.compiler.source=1.8

+ 4 - 0
.settings/org.eclipse.m2e.core.prefs

@@ -0,0 +1,4 @@
1
+activeProfiles=
2
+eclipse.preferences.version=1
3
+resolveWorkspaceProjects=true
4
+version=1

BIN
lib/Cortix7_fingerprinter.jar


BIN
lib/Cortix7_sharedEntities.jar


Diferenças do arquivo suprimidas por serem muito extensas
+ 1065 - 0
log.out


+ 12 - 0
log4j.properties

@@ -0,0 +1,12 @@
1
+log4j.rootLogger=DEBUG, stdout, logfile
2
+
3
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
4
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
5
+log4j.appender.stdout.layout.ConversionPattern=%d %p (%t) [%c] - %m%n
6
+
7
+log4j.appender.logfile=org.apache.log4j.RollingFileAppender
8
+log4j.appender.logfile.File=log.out
9
+log4j.appender.logfile.MaxFileSize=100MB
10
+log4j.appender.logfile.MaxBackupIndex=4
11
+log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
12
+log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

+ 52 - 0
pom.xml

@@ -0,0 +1,52 @@
1
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3
+	<modelVersion>4.0.0</modelVersion>
4
+	<groupId>br.com.soundview</groupId>
5
+	<artifactId>BKE_SVSpotDiscoverer</artifactId>
6
+	<version>0.0.1-SNAPSHOT</version>
7
+	<name>BKE_SVSpotDiscoverer</name>
8
+
9
+	<dependencies>
10
+
11
+		<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
12
+		<dependency>
13
+			<groupId>commons-io</groupId>
14
+			<artifactId>commons-io</artifactId>
15
+			<version>2.5</version>
16
+		</dependency>
17
+
18
+		<!-- https://mvnrepository.com/artifact/junit/junit -->
19
+		<dependency>
20
+			<groupId>junit</groupId>
21
+			<artifactId>junit</artifactId>
22
+			<version>4.12</version>
23
+		</dependency>
24
+
25
+		<!-- https://mvnrepository.com/artifact/org/jaudiotagger -->
26
+		<dependency>
27
+			<groupId>org</groupId>
28
+			<artifactId>jaudiotagger</artifactId>
29
+			<version>2.0.3</version>
30
+		</dependency>
31
+
32
+		<!-- https://mvnrepository.com/artifact/log4j/log4j -->
33
+		<dependency>
34
+			<groupId>log4j</groupId>
35
+			<artifactId>log4j</artifactId>
36
+			<version>1.2.17</version>
37
+		</dependency>
38
+
39
+		<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
40
+		<dependency>
41
+			<groupId>org.mockito</groupId>
42
+			<artifactId>mockito-all</artifactId>
43
+			<version>1.10.19</version>
44
+		</dependency>
45
+
46
+	</dependencies>
47
+
48
+	<properties>
49
+		<java.version>1.8</java.version>
50
+	</properties>
51
+
52
+</project>

+ 32 - 0
src/main/java/bean/Content.java

@@ -0,0 +1,32 @@
1
+package bean;
2
+
3
+import java.io.File;
4
+
5
+public class Content {
6
+
7
+	private final int contentId;
8
+	private final File contentFile;
9
+	
10
+	public Content(int contentId, File contentFile) {	
11
+		this.contentId = contentId;
12
+		this.contentFile = contentFile;
13
+	}
14
+
15
+	public int getContentId() {
16
+		return contentId;
17
+	}
18
+
19
+	public File getContentFile() {
20
+		return contentFile;
21
+	}
22
+	
23
+	@Override
24
+	public String toString() {
25
+		
26
+		return new StringBuilder("Id: ")
27
+				.append(this.contentId)
28
+				.append(", file: ")
29
+				.append(this.contentFile.getName())
30
+				.toString();
31
+	}
32
+}

+ 28 - 0
src/main/java/bean/InnoResult.java

@@ -0,0 +1,28 @@
1
+package bean;
2
+
3
+public class InnoResult {
4
+
5
+	private final int contentId;
6
+	private final double distance;
7
+	public InnoResult(int contentId, double distance) {
8
+		super();
9
+		this.contentId = contentId;
10
+		this.distance = distance;
11
+	}
12
+	public int getContentId() {
13
+		return contentId;
14
+	}
15
+	public double getDistance() {
16
+		return distance;
17
+	}
18
+	
19
+	@Override
20
+	public String toString() {
21
+		
22
+		return new StringBuilder("content: ")
23
+				.append(this.contentId)
24
+				.append(", distance: ")
25
+				.append(this.distance)
26
+				.toString();
27
+	}
28
+}

+ 26 - 0
src/main/java/bean/Track.java

@@ -0,0 +1,26 @@
1
+package bean;
2
+
3
+import java.io.File;
4
+import java.time.LocalDateTime;
5
+
6
+public class Track {
7
+
8
+	private final int id;
9
+	private final File track;
10
+	private final LocalDateTime creationTime;
11
+	
12
+	public Track(int id, File track, LocalDateTime creationTime) {		
13
+		this.id = id;
14
+		this.track = track;
15
+		this.creationTime = creationTime;
16
+	}
17
+	public int getId() {
18
+		return id;
19
+	}
20
+	public File getTrack() {
21
+		return track;
22
+	}
23
+	public LocalDateTime getCreationTime() {
24
+		return creationTime;
25
+	}	
26
+}

+ 148 - 0
src/main/java/extraction/SingleTrackExtractor.java

@@ -0,0 +1,148 @@
1
+package extraction;
2
+
3
+import java.io.File;
4
+import java.io.IOException;
5
+import java.lang.ProcessBuilder.Redirect;
6
+import java.time.LocalTime;
7
+import java.time.format.DateTimeFormatter;
8
+import java.util.Arrays;
9
+import java.util.concurrent.TimeUnit;
10
+
11
+import org.apache.log4j.Logger;
12
+
13
+public class SingleTrackExtractor {
14
+
15
+	private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
16
+	
17
+	private static final Logger logger = Logger.getLogger(SingleTrackExtractor.class);
18
+
19
+	private final File destinationDirectory;
20
+
21
+	private final File temporaryLogFile = new File("/tmp/SingleTrackExtractor.log");
22
+
23
+	/**
24
+	 * Constructor.
25
+	 * 
26
+	 * @param destinationDirectory File pointing at the directory where the tracks must be stored.
27
+	 * 
28
+	 * @throws IOException thrown if destination directory does not exist and cannot be created.
29
+	 */
30
+	public SingleTrackExtractor(File destinationDirectory) throws IOException {
31
+
32
+		if (destinationDirectory == null) {
33
+			throw new NullPointerException("Null destination directory path.");
34
+		}
35
+
36
+		if (!destinationDirectory.isDirectory()) {
37
+			throw new IllegalArgumentException("Invalid destination directory: "+destinationDirectory.getAbsolutePath());
38
+		}
39
+
40
+		if (!destinationDirectory.exists()) {
41
+
42
+			logger.warn("constructor: Inexistent destination directory: '"+destinationDirectory.getAbsolutePath()+"'. Going to create it.");
43
+
44
+			if (destinationDirectory.mkdirs()) {
45
+
46
+				logger.info("SingleTrackExtractor: Destination directory successfully created: "+destinationDirectory.getAbsolutePath());
47
+			}
48
+			else {
49
+				throw new IOException("Could not create destination directory at '"+destinationDirectory.getAbsolutePath()+"'.");
50
+			}
51
+		}		
52
+
53
+		this.destinationDirectory = destinationDirectory;
54
+
55
+		logger.info("SingleTrackExtractor: Destination directory set to '"+this.destinationDirectory.getAbsolutePath()+"'");
56
+		logger.info("SingleTrackExtractor: Temporary log file for this class created at '"+this.temporaryLogFile.getAbsolutePath()+"'");
57
+	}	
58
+
59
+	public File extract(File sourceAudioFile, LocalTime startTime, LocalTime duration, String trackName) {
60
+
61
+		if (sourceAudioFile == null) {
62
+			logger.error("extract: Null source audio file.");
63
+			return null;
64
+		}
65
+		
66
+		if (!sourceAudioFile.exists() || !sourceAudioFile.isFile()) {
67
+			logger.error("extract: Invalid source audio file.");
68
+			return null;
69
+		}
70
+		
71
+		if (startTime == null) {
72
+			logger.error("extract: Null start time.");
73
+			return null;
74
+		}
75
+		
76
+		if (duration == null) {
77
+			logger.error("extract: Null duration.");
78
+			return null;
79
+		}
80
+		
81
+		if (trackName == null) {
82
+			logger.error("extract: Null track name.");
83
+			return null;
84
+		}
85
+		
86
+		if (trackName.trim().isEmpty()) {
87
+			logger.error("extract: Empty track name.");
88
+			return null;
89
+		}
90
+		
91
+		String destinationTrackPath = new File(this.destinationDirectory, trackName).getAbsolutePath();							
92
+
93
+		String command = this.createCommand(sourceAudioFile, startTime, duration, destinationTrackPath);
94
+
95
+		ProcessBuilder builder = new ProcessBuilder(Arrays.asList(new String[]{
96
+				"/bin/bash",
97
+				"-c",
98
+				command
99
+		}));
100
+
101
+		builder.redirectOutput(Redirect.appendTo(this.temporaryLogFile));
102
+		builder.redirectError(this.temporaryLogFile);
103
+
104
+		String invocationDescription = new StringBuilder("Source: ")
105
+				.append(sourceAudioFile.getAbsolutePath())
106
+				.append(", start: ")
107
+				.append(startTime)
108
+				.append(", duration: ")
109
+				.append(duration)
110
+				.toString();
111
+		
112
+		try {
113
+			Process process = builder.start();					
114
+
115
+			if (process.waitFor(30, TimeUnit.SECONDS)) {
116
+				logger.info("extract: "+invocationDescription+". Successfully extracted: "+destinationTrackPath);
117
+			}
118
+			else {
119
+				logger.error("extract: "+invocationDescription+". Could not execute: "+command);
120
+			}
121
+		}
122
+		catch (Exception e) {
123
+			logger.error("extract: Could not extract: "+invocationDescription);
124
+		}
125
+		
126
+		File extractedTrackFile = new File(destinationTrackPath);
127
+		
128
+		if (!extractedTrackFile.exists()) {
129
+			
130
+			logger.error("generateFilePath: Process completed normally but track was not extracted: "+destinationTrackPath);
131
+			
132
+			return null;
133
+		}
134
+		
135
+		return extractedTrackFile;
136
+	}
137
+
138
+	private final String createCommand(File audioFile, LocalTime start, LocalTime duration, String destinationFile) {
139
+
140
+		return new StringBuilder("ffmpeg -i ")
141
+				.append(audioFile.getAbsolutePath())
142
+				.append(" -t ").append(this.formatter.format(duration))
143
+				.append(" -ss ").append(this.formatter.format(start))
144
+				.append(" -acodec ")
145
+				.append(" copy ").append(destinationFile)
146
+				.toString();
147
+	}
148
+}

+ 68 - 0
src/main/java/extraction/TrackExtractor.java

@@ -0,0 +1,68 @@
1
+package extraction;
2
+
3
+import java.io.File;
4
+import java.time.LocalDateTime;
5
+import java.time.LocalTime;
6
+import java.time.format.DateTimeFormatter;
7
+import java.util.ArrayList;
8
+import java.util.List;
9
+
10
+import org.apache.log4j.Logger;
11
+
12
+import bean.Track;
13
+import utils.AudioUtils;
14
+
15
+public class TrackExtractor {
16
+	
17
+	private static final Logger logger = Logger.getLogger(TrackExtractor.class);
18
+	
19
+	private final SingleTrackExtractor singleTrackExtractor;
20
+	
21
+	private final DateTimeFormatter formatter;
22
+	
23
+	private final AudioUtils audioUtils;
24
+	
25
+	public TrackExtractor(SingleTrackExtractor singleTrackExtractor) {
26
+		
27
+		this.singleTrackExtractor = singleTrackExtractor;
28
+		this.audioUtils = new AudioUtils();
29
+		this.formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");;
30
+	}
31
+
32
+	public List<Track> extract(File audioFile, int trackLengthSecs, LocalDateTime initialDateTime) {
33
+
34
+		int idGenerator = 0;
35
+		
36
+		LocalDateTime localDateTime = initialDateTime;
37
+		
38
+		List<Track> extractedTracks = new ArrayList<>();
39
+		
40
+		LocalTime initialTime = LocalTime.of(0, 0, 0);
41
+		LocalTime duration = LocalTime.MIN.plusSeconds(trackLengthSecs);
42
+		
43
+		String trackName = this.formatter.format(localDateTime) + ".mp3";
44
+		
45
+		File extractedTrack = null;
46
+		
47
+		int totalTracks = (int)this.audioUtils.getMp3DurationInSeconds(audioFile) / trackLengthSecs;
48
+		
49
+		for (int i = 0 ; i < totalTracks ; i++) {
50
+		
51
+			logger.info("extract: Going to extract "+trackName);
52
+			
53
+			extractedTrack = this.singleTrackExtractor.extract(audioFile, initialTime, duration, trackName);			
54
+			
55
+			extractedTracks.add(new Track(idGenerator, extractedTrack, localDateTime));
56
+			
57
+			localDateTime = localDateTime.plusSeconds(trackLengthSecs);
58
+			
59
+			initialTime = initialTime.plusSeconds(trackLengthSecs);
60
+			
61
+			trackName = this.formatter.format(localDateTime) + ".mp3";		
62
+		}
63
+		
64
+		logger.info("extract: Extracted "+extractedTracks.size()+" tracks from '"+audioFile.getAbsolutePath()+"' with length "+trackLengthSecs+" seconds, starting at "+initialDateTime);
65
+		
66
+		return extractedTracks;
67
+	}
68
+}

+ 141 - 0
src/main/java/search/inno/InnoSearcher.java

@@ -0,0 +1,141 @@
1
+package search.inno;
2
+
3
+import java.io.File;
4
+import java.time.LocalDateTime;
5
+import java.time.ZoneId;
6
+import java.util.ArrayList;
7
+import java.util.Date;
8
+import java.util.List;
9
+
10
+import org.apache.log4j.Logger;
11
+
12
+import bean.Content;
13
+import bean.InnoResult;
14
+import bean.Track;
15
+import br.com.innovox.apps.managers.ExtractorManager;
16
+import br.com.innovox.apps.managers.FinderManager;
17
+import br.com.innovox.apps.managers.LutManager;
18
+import br.com.innovox.lib.soundmatch.IdtResult;
19
+import utils.AudioUtils;
20
+
21
+public class InnoSearcher {
22
+
23
+	private static final Logger logger = Logger.getLogger(InnoSearcher.class);
24
+
25
+	private LutManager lut;
26
+	private ExtractorManager extractor;
27
+	private FinderManager finder;
28
+	private AudioUtils audioUtils;
29
+
30
+	public InnoSearcher() {
31
+		
32
+		this.initLut();
33
+
34
+		this.audioUtils = new AudioUtils();
35
+	}
36
+	
37
+	private final void initLut() {
38
+		
39
+		logger.info("initLut: Initializing Lut.");
40
+		
41
+		this.extractor = new ExtractorManager();
42
+		this.lut = new LutManager(LutManager.DB_MEDIUM_SIZE);
43
+		this.finder = new FinderManager(lut);		
44
+	}
45
+	
46
+	public boolean indexAll(List<Content> contents) {
47
+
48
+		int indexCount = 0;
49
+
50
+		for (Content content : contents) {
51
+
52
+			try {
53
+				byte[] wav = this.audioUtils.toWave(content.getContentFile());
54
+
55
+				if (wav == null) {
56
+					logger.error("index: Could not generate WAV data from "+content.getContentFile().getAbsolutePath());
57
+					continue;
58
+				}
59
+
60
+				byte[] fingerprint = ExtractorManager.Process(wav);
61
+
62
+				this.lut.AddFingerprint(fingerprint, content.getContentId());
63
+
64
+				logger.info("index: Indexed "+content.getContentFile().getName()+" (" + ++indexCount + "/" + contents.size() + ")");
65
+			}
66
+			catch (Exception e) {
67
+				logger.error("indexAll: Could not index: "+content, e);
68
+				return false;
69
+			}
70
+		}
71
+		
72
+		logger.info("indexAll: Indexed "+indexCount+" contents.");
73
+		
74
+		return true;
75
+	}
76
+
77
+	public List<InnoResult> search(Track track) {
78
+		
79
+		List<InnoResult> result = new ArrayList<InnoResult>();
80
+		
81
+		IdtResult[] searchResult = null;
82
+		
83
+		try {
84
+			searchResult = this.search(track.getTrack(), this.toDate(track.getCreationTime()));
85
+		} catch (Exception e) {
86
+			logger.error("search: Could not perform search for track: "+track);
87
+		}
88
+		
89
+		if (searchResult != null) {
90
+			
91
+			for (IdtResult idtResult : searchResult) {
92
+				
93
+				for (int i = 0 ; i < idtResult.ids.length ; i++) {
94
+					
95
+					if (idtResult.ids[i] != 0) {
96
+						
97
+						result.add(new InnoResult(idtResult.ids[i], idtResult.distances[i]));
98
+					}
99
+				}
100
+			}
101
+		}
102
+		
103
+		return result;
104
+	}
105
+
106
+	private IdtResult[] search(File query, Date queryCreationTime) throws Exception  {
107
+		
108
+		logger.info("search: Searching "+query.getAbsolutePath());
109
+
110
+		// Reading wav file
111
+		byte[] wavstream = this.audioUtils.toWave(query);
112
+		if (wavstream == null) {
113
+			logger.error("search: Could not generate WAV data from "+query.getAbsolutePath());
114
+			return null;
115
+		}		
116
+
117
+		// Processing -- Feature Extration
118
+		wavstream = extractor.AlingWave(wavstream, queryCreationTime);
119
+		
120
+		if (wavstream == null) {
121
+			logger.error("search: Could not align WAV data from "+query.getAbsolutePath());
122
+			return null;
123
+		}
124
+
125
+		byte[] fingerprint = extractor.Process(wavstream, ExtractorManager.FLAG_NORMAL_FRAME);
126
+		
127
+		return finder.SearchMany(fingerprint, FinderManager.FLAG_NORMAL_FRAME);		
128
+	}
129
+	
130
+	private final Date toDate(LocalDateTime date) {
131
+		
132
+		return Date.from(date.atZone(ZoneId.systemDefault()).toInstant());
133
+	}
134
+
135
+	public void clear() {
136
+		
137
+		logger.info("clear: Cleaning InnoSearcher index.");
138
+		
139
+		this.initLut();
140
+	}
141
+}

+ 79 - 0
src/main/java/utils/AudioUtils.java

@@ -0,0 +1,79 @@
1
+package utils;
2
+
3
+import java.io.File;
4
+
5
+import org.apache.log4j.Logger;
6
+import org.jaudiotagger.audio.AudioFile;
7
+import org.jaudiotagger.audio.AudioFileIO;
8
+
9
+import com.musicg.dsp.Resampler;
10
+import com.musicg.wave.Wave;
11
+
12
+import utils.audio.AudioMethods;
13
+
14
+public class AudioUtils {
15
+		
16
+	private static final Logger logger = Logger.getLogger(AudioUtils.class);
17
+	
18
+	private final AudioMethods audioMethods;
19
+	private final Resampler resampler;
20
+	
21
+	public AudioUtils() {
22
+		this.audioMethods = new AudioMethods();
23
+		this.resampler = new Resampler();
24
+	}
25
+	
26
+	/**
27
+	 * Retrieves the duration of an MP3 file in seconds.
28
+	 * 
29
+	 * @param mp3File File pointing at the MP3 file.
30
+	 * 
31
+	 * @return double containing the duration or -1 in case the duration could not be retrieved.
32
+	 */
33
+	public final double getMp3DurationInSeconds(File mp3File) {
34
+
35
+		double errorCode = -1;
36
+		
37
+		if (mp3File == null) {
38
+			logger.error("getMp3DurationInSeconds: Null MP3 file.");
39
+			return errorCode;
40
+		}
41
+		
42
+		if (!mp3File.exists() || !mp3File.isFile()) {
43
+			logger.error("getMp3DurationInSeconds: Invalid MP3 file: "+mp3File.getAbsolutePath());
44
+			return errorCode;
45
+		}
46
+		
47
+		try {
48
+			AudioFile audioFile = AudioFileIO.read(mp3File);
49
+			
50
+			double duration = ((double)mp3File.length()) / (audioFile.getAudioHeader().getBitRateAsNumber() / 8);
51
+			
52
+			return duration / 1000;
53
+		} 
54
+		catch (Exception e) {
55
+			logger.error("getMp3DurationInSeconds: Could not retrieve duration from "+mp3File.getAbsolutePath(), e);
56
+			return errorCode;
57
+		}		
58
+	}
59
+	
60
+	public final byte[] toWave(File file) throws Exception {
61
+
62
+		String wavFile = audioMethods.encodeWAVNRenameKeepOriginal(file.getAbsolutePath(), true);
63
+
64
+		if (wavFile == null || !new File(wavFile).exists()) {
65
+			System.out.println("Could not process: "+wavFile);
66
+			return null;
67
+		}
68
+
69
+		Wave wave = new Wave(wavFile);
70
+
71
+		int sourceRate = wave.getWaveHeader().getSampleRate();		
72
+
73
+		byte[] wavstream = resampler.reSample(wave.getBytes(), wave.getWaveHeader().getBitsPerSample(), sourceRate, 8000);
74
+
75
+		new File(wavFile).delete();
76
+
77
+		return wavstream;
78
+	}
79
+}

+ 14 - 0
src/test/java/constants/TestConstants.java

@@ -0,0 +1,14 @@
1
+package constants;
2
+
3
+import java.io.File;
4
+
5
+public class TestConstants {
6
+
7
+	public final static File tempDir = new File("tempTestDir");
8
+	
9
+	public final static File testAudioFile = new File("testFiles/1_20171114114500.mp3");
10
+
11
+	public final static int testAudioFileDurationSecs = 89;
12
+	
13
+	public final static File[] audioToSearch = new File("testFiles/search").listFiles();
14
+}

+ 72 - 0
src/test/java/extraction/TestSingleTrackExtractor.java

@@ -0,0 +1,72 @@
1
+package extraction;
2
+
3
+import java.io.File;
4
+import java.time.LocalTime;
5
+
6
+import org.apache.commons.io.FileUtils;
7
+import org.apache.log4j.PropertyConfigurator;
8
+import org.junit.After;
9
+import org.junit.AfterClass;
10
+import static org.junit.Assert.*;
11
+import org.junit.Before;
12
+import org.junit.BeforeClass;
13
+import org.junit.Test;
14
+
15
+import constants.TestConstants;
16
+import utils.AudioUtils;
17
+
18
+public class TestSingleTrackExtractor {
19
+	
20
+	private final AudioUtils audioUtils = new AudioUtils();
21
+	
22
+	private SingleTrackExtractor extractor = null;
23
+	
24
+	@BeforeClass
25
+	public static void setUpBeforeClass() throws Exception {
26
+		PropertyConfigurator.configure("log4j.properties");
27
+	}
28
+
29
+	@AfterClass
30
+	public static void tearDownAfterClass() throws Exception {
31
+	}
32
+
33
+	@Before
34
+	public void setUp() throws Exception {
35
+		TestConstants.tempDir.mkdirs();
36
+		extractor = new SingleTrackExtractor(TestConstants.tempDir);
37
+	}
38
+
39
+	@After
40
+	public void tearDown() throws Exception {
41
+		FileUtils.deleteDirectory(TestConstants.tempDir);
42
+	}
43
+
44
+	@Test
45
+	public void should_return_empty_if_data_out_of_range() {
46
+			
47
+		File track = extractor.extract(TestConstants.testAudioFile, LocalTime.of(0, 2, 20), LocalTime.of(0, 0, 20), "track.mp3");
48
+		
49
+		assertEquals(0, this.audioUtils.getMp3DurationInSeconds(track), 1);
50
+	}
51
+	
52
+	@Test
53
+	public void should_not_accept_invalid_parameters() {
54
+	
55
+		assertNull(extractor.extract(null, LocalTime.now(), LocalTime.now(), "track.mp3"));	
56
+		
57
+		assertNull(extractor.extract(TestConstants.testAudioFile, null, null, "track.mp3"));
58
+		
59
+		assertNull(extractor.extract(new File("inexistent"), LocalTime.of(0, 0, 20), LocalTime.of(0, 0, 20), "track.mp3"));
60
+	}
61
+	
62
+	@Test
63
+	public void should_extract_track_if_everything_valid() {
64
+	
65
+		LocalTime start = LocalTime.of(0, 0, 20);
66
+		LocalTime duration = LocalTime.of(0, 0, 10);
67
+		
68
+		File track = extractor.extract(TestConstants.testAudioFile, start, duration, "track.mp3");		
69
+		
70
+		assertEquals(10, this.audioUtils.getMp3DurationInSeconds(track), 1);
71
+	}
72
+} 

+ 92 - 0
src/test/java/extraction/TestTrackExtractor.java

@@ -0,0 +1,92 @@
1
+package extraction;
2
+
3
+import static org.junit.Assert.assertEquals;
4
+
5
+import java.io.File;
6
+import java.time.LocalDateTime;
7
+import java.time.format.DateTimeFormatter;
8
+import java.util.List;
9
+
10
+import org.apache.commons.io.FileUtils;
11
+import org.apache.log4j.PropertyConfigurator;
12
+import org.junit.After;
13
+import org.junit.AfterClass;
14
+import org.junit.Before;
15
+import org.junit.BeforeClass;
16
+import org.junit.Test;
17
+
18
+import bean.Track;
19
+import constants.TestConstants;
20
+import utils.AudioUtils;
21
+
22
+public class TestTrackExtractor {
23
+	
24
+	private final AudioUtils audioUtils = new AudioUtils();
25
+	private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
26
+	
27
+	@BeforeClass
28
+	public static void setUpBeforeClass() throws Exception {
29
+				
30
+		PropertyConfigurator.configure("log4j.properties");
31
+		FileUtils.deleteDirectory(TestConstants.tempDir);
32
+	}
33
+
34
+	@AfterClass
35
+	public static void tearDownAfterClass() throws Exception {
36
+	}
37
+
38
+	@Before
39
+	public void setUp() throws Exception {
40
+		
41
+		TestConstants.tempDir.mkdirs();
42
+	}
43
+
44
+	@After
45
+	public void tearDown() throws Exception {
46
+		
47
+		FileUtils.deleteDirectory(TestConstants.tempDir);
48
+	}
49
+
50
+	@Test
51
+	public void should_extract_all_tracks() throws Exception {
52
+	
53
+		SingleTrackExtractor singleExtractor = new SingleTrackExtractor(TestConstants.tempDir);
54
+		
55
+		int trackLength = this.findLargestDivisor(TestConstants.testAudioFileDurationSecs);
56
+		
57
+		TrackExtractor extractor = new TrackExtractor(singleExtractor);
58
+		
59
+		LocalDateTime initialTime = this.parseCreationTime(TestConstants.testAudioFile);
60
+		
61
+		List<Track> tracks = extractor.extract(TestConstants.testAudioFile, trackLength, initialTime);
62
+		
63
+		assertEquals(TestConstants.testAudioFileDurationSecs / trackLength, tracks.size());		
64
+		
65
+		for (Track track : tracks) {
66
+			
67
+			assertEquals(trackLength, audioUtils.getMp3DurationInSeconds(track.getTrack()), 1);
68
+			
69
+			assertEquals(initialTime, track.getCreationTime());
70
+			
71
+			initialTime = initialTime.plusSeconds(trackLength);
72
+		}
73
+	}
74
+	
75
+	private final LocalDateTime parseCreationTime(File audioFile) {
76
+		
77
+		return LocalDateTime.parse(audioFile.getName().replaceAll(".mp3", "").split("_")[1], formatter);
78
+	}
79
+	
80
+	private final int findLargestDivisor(int length) {
81
+		
82
+		if (length % 2 == 0) {
83
+			return length / 2;
84
+		}
85
+		
86
+		int divisor = length / 2;
87
+		
88
+		while (--divisor > 1 && length % divisor != 0);
89
+		
90
+		return divisor;
91
+	}
92
+}

+ 200 - 0
src/test/java/search/inno/TestInnoSearcher.java

@@ -0,0 +1,200 @@
1
+package search.inno;
2
+
3
+import static org.junit.Assert.assertEquals;
4
+import static org.junit.Assert.assertTrue;
5
+
6
+import java.io.File;
7
+import java.time.LocalDateTime;
8
+import java.util.ArrayList;
9
+import java.util.List;
10
+
11
+import org.apache.log4j.Logger;
12
+import org.apache.log4j.PropertyConfigurator;
13
+import org.junit.After;
14
+import org.junit.AfterClass;
15
+import org.junit.Before;
16
+import org.junit.BeforeClass;
17
+import org.junit.Test;
18
+
19
+import bean.Content;
20
+import bean.InnoResult;
21
+import bean.Track;
22
+import constants.TestConstants;
23
+
24
+public class TestInnoSearcher {
25
+	
26
+	private static final Logger logger = Logger.getLogger(TestInnoSearcher.class);
27
+	
28
+	@BeforeClass
29
+	public static void setUpBeforeClass() throws Exception {
30
+		PropertyConfigurator.configure("log4j.properties");
31
+	}
32
+
33
+	@AfterClass
34
+	public static void tearDownAfterClass() throws Exception {
35
+	}
36
+
37
+	@Before
38
+	public void setUp() throws Exception {
39
+	}
40
+
41
+	@After
42
+	public void tearDown() throws Exception {
43
+	}
44
+
45
+	@Test
46
+	public void should_identify_all_if_all_is_indexed() {
47
+	
48
+		List<Content> contents = new ArrayList<Content>();
49
+		for (File file : TestConstants.audioToSearch) {
50
+			contents.add(new Content(this.getContentId(file), file));
51
+		}
52
+		
53
+		InnoSearcher searcher = new InnoSearcher();
54
+		
55
+		searcher.indexAll(contents);
56
+				
57
+		int seconds = 10;
58
+		LocalDateTime initial = LocalDateTime.now();
59
+		
60
+		for (Content content : contents) {
61
+			
62
+			List<InnoResult> result = searcher.search(new Track(1, content.getContentFile(), initial));	
63
+			
64
+			assertTrue(!result.isEmpty());
65
+			
66
+			for (InnoResult innoResult : result) {
67
+
68
+				logger.info("should_identify_all_if_all_is_indexed: Expected ("+content+"), actual ("+innoResult+")");
69
+				
70
+				assertEquals(content.getContentId(), innoResult.getContentId(), 1);
71
+			}
72
+			
73
+			initial = initial.plusSeconds(seconds);
74
+		}
75
+	}
76
+	
77
+	@Test
78
+	public void should_only_identify_indexed_content() {
79
+		
80
+		List<Content> contents = new ArrayList<Content>();
81
+		contents.add(new Content(this.getContentId(TestConstants.audioToSearch[0]), TestConstants.audioToSearch[0]));
82
+		
83
+		InnoSearcher searcher = new InnoSearcher();
84
+		
85
+		searcher.indexAll(contents);
86
+		
87
+		List<InnoResult> result = searcher.search(new Track(1, TestConstants.audioToSearch[0], LocalDateTime.now()));
88
+		
89
+		assertTrue(!result.isEmpty());
90
+		
91
+		for (InnoResult content : result) {
92
+			
93
+			assertEquals(contents.get(0).getContentId(), content.getContentId());
94
+		}
95
+		
96
+		result = searcher.search(new Track(3, TestConstants.audioToSearch[2], LocalDateTime.now()));
97
+		
98
+		assertTrue(result.isEmpty());
99
+	}
100
+	
101
+	@Test
102
+	public void should_accept_on_the_fly_indexing() throws Exception {
103
+		
104
+		List<Content> contents = new ArrayList<Content>();
105
+		contents.add(new Content(this.getContentId(TestConstants.audioToSearch[0]), TestConstants.audioToSearch[0]));
106
+		
107
+		InnoSearcher searcher = new InnoSearcher();
108
+		
109
+		searcher.indexAll(contents);
110
+		
111
+		List<InnoResult> result = searcher.search(new Track(1, TestConstants.audioToSearch[0], LocalDateTime.now()));
112
+		
113
+		assertTrue(!result.isEmpty());
114
+		
115
+		for (InnoResult content : result) {
116
+			
117
+			assertEquals(contents.get(0).getContentId(), content.getContentId());
118
+		}
119
+		
120
+		result = searcher.search(new Track(3, TestConstants.audioToSearch[2], LocalDateTime.now()));
121
+				
122
+		assertTrue(result.isEmpty());
123
+		
124
+		contents.clear();
125
+		
126
+		contents.add(new Content(this.getContentId(TestConstants.audioToSearch[2]), TestConstants.audioToSearch[2]));
127
+		
128
+		searcher.indexAll(contents);
129
+		
130
+		Thread.sleep(2000);
131
+		
132
+		result = searcher.search(new Track(3, TestConstants.audioToSearch[2], LocalDateTime.now()));
133
+		
134
+		assertTrue(!result.isEmpty());
135
+		
136
+		for (InnoResult content : result) {
137
+			
138
+			assertEquals(contents.get(0).getContentId(), content.getContentId());
139
+		}
140
+	}
141
+	
142
+	@Test
143
+	public void should_return_nothing_if_index_is_empty() {
144
+		
145
+		InnoSearcher searcher = new InnoSearcher();
146
+		
147
+		for (File file : TestConstants.audioToSearch) {
148
+			
149
+			List<InnoResult> result = searcher.search(new Track(1, file, LocalDateTime.now()));
150
+			
151
+			assertTrue(result.isEmpty());
152
+		}		
153
+	}
154
+	
155
+	@Test
156
+	public void should_be_able_to_clean_up_index() {		
157
+		
158
+		List<Content> contents = new ArrayList<Content>();
159
+		for (File file : TestConstants.audioToSearch) {
160
+			contents.add(new Content(this.getContentId(file), file));
161
+		}
162
+		
163
+		InnoSearcher searcher = new InnoSearcher();
164
+		
165
+		searcher.indexAll(contents);
166
+				
167
+		int seconds = 10;
168
+		LocalDateTime initial = LocalDateTime.now();
169
+		
170
+		for (Content content : contents) {
171
+			
172
+			List<InnoResult> result = searcher.search(new Track(1, content.getContentFile(), initial));	
173
+			
174
+			assertTrue(!result.isEmpty());
175
+			
176
+			for (InnoResult innoResult : result) {
177
+
178
+				logger.info("should_identify_all_if_all_is_indexed: Expected ("+content+"), actual ("+innoResult+")");
179
+				
180
+				assertEquals(content.getContentId(), innoResult.getContentId(), 1);
181
+			}
182
+			
183
+			initial = initial.plusSeconds(seconds);
184
+		}
185
+		
186
+		searcher.clear();
187
+		
188
+		for (Content content : contents) {
189
+			
190
+			List<InnoResult> result = searcher.search(new Track(1, content.getContentFile(), initial));
191
+			
192
+			assertTrue(result.isEmpty());
193
+		}
194
+	}
195
+	
196
+	private final int getContentId(File content) {
197
+		
198
+		return Integer.parseInt(content.getName().replaceAll(".mp3", ""));
199
+	}
200
+}

+ 50 - 0
src/test/java/utils/TestAudioUtils.java

@@ -0,0 +1,50 @@
1
+package utils;
2
+
3
+import static org.junit.Assert.assertEquals;
4
+
5
+import java.io.File;
6
+
7
+import org.junit.After;
8
+import org.junit.AfterClass;
9
+import org.junit.Before;
10
+import org.junit.BeforeClass;
11
+import org.junit.Test;
12
+
13
+import constants.TestConstants;
14
+
15
+public class TestAudioUtils {
16
+
17
+	private final AudioUtils audioUtils = new AudioUtils();
18
+	
19
+	@BeforeClass
20
+	public static void setUpBeforeClass() throws Exception {
21
+	}
22
+
23
+	@AfterClass
24
+	public static void tearDownAfterClass() throws Exception {
25
+	}
26
+
27
+	@Before
28
+	public void setUp() throws Exception {
29
+	}
30
+
31
+	@After
32
+	public void tearDown() throws Exception {
33
+	}
34
+
35
+	@Test
36
+	public void should_return_correct_length() {
37
+	
38
+		double duration = audioUtils.getMp3DurationInSeconds(TestConstants.testAudioFile);
39
+		
40
+		assertEquals(89, duration, 1);
41
+	}
42
+	
43
+	@Test
44
+	public void should_return_negative_value_for_invalid_input() {
45
+		
46
+		assertEquals(-1, audioUtils.getMp3DurationInSeconds(null), 0);
47
+		
48
+		assertEquals(-1, audioUtils.getMp3DurationInSeconds(new File("inexistent")), 0);
49
+	}
50
+}

BIN
testFiles/1_20171114114500.mp3


BIN
testFiles/search/1.mp3


BIN
testFiles/search/2.mp3


BIN
testFiles/search/3.mp3