1<?php
2/////////////////////////////////////////////////////////////////
3/// getID3() by James Heinrich <info@getid3.org>               //
4//  available at https://github.com/JamesHeinrich/getID3       //
5//            or https://www.getid3.org                        //
6//            or http://getid3.sourceforge.net                 //
7//                                                             //
8// /demo/demo.joinmp3.php - part of getID3()                   //
9// Sample script for splicing two or more MP3s together into   //
10// one file. Does not attempt to fix VBR header frames.        //
11// Can also be used to extract portion from single file.       //
12//  see readme.txt for more details                            //
13//                                                            ///
14/////////////////////////////////////////////////////////////////
15
16
17// sample usage:
18//   $FilenameOut   = 'combined.mp3';
19//   $FilenamesIn[] = 'first.mp3';                    // filename with no start/length parameters
20//   $FilenamesIn[] = array('second.mp3',   0,   0);  // filename with zero for start/length is the same as not specified (start = beginning, length = full duration)
21//   $FilenamesIn[] = array('third.mp3',    0,  10);  // extract first 10 seconds of audio
22//   $FilenamesIn[] = array('fourth.mp3', -10,   0);  // extract last 10 seconds of audio
23//   $FilenamesIn[] = array('fifth.mp3',   10,   0);  // extract everything except first 10 seconds of audio
24//   $FilenamesIn[] = array('sixth.mp3',    0, -10);  // extract everything except last 10 seconds of audio
25//   if (CombineMultipleMP3sTo($FilenameOut, $FilenamesIn)) {
26//       echo 'Successfully copied '.implode(' + ', $FilenamesIn).' to '.$FilenameOut;
27//   } else {
28//       echo 'Failed to copy '.implode(' + ', $FilenamesIn).' to '.$FilenameOut;
29//   }
30//
31// Could also be called like this to extract portion from single file:
32//   CombineMultipleMP3sTo('sample.mp3', array(array('input.mp3', 0, 30))); // extract first 30 seconds of audio
33
34
35function CombineMultipleMP3sTo($FilenameOut, $FilenamesIn) {
36
37	foreach ($FilenamesIn as $nextinputfilename) {
38		if (is_array($nextinputfilename)) {
39			$nextinputfilename = $nextinputfilename[0];
40		}
41		if (!is_readable($nextinputfilename)) {
42			echo 'Cannot read "'.$nextinputfilename.'"<BR>';
43			return false;
44		}
45	}
46	if ((file_exists($FilenameOut) && !is_writeable($FilenameOut)) || (!file_exists($FilenameOut) && !is_writeable(dirname($FilenameOut)))) {
47		echo 'Cannot write "'.$FilenameOut.'"<BR>';
48		return false;
49	}
50
51	require_once(dirname(__FILE__).'/../getid3/getid3.php');
52	ob_start();
53	if ($fp_output = fopen($FilenameOut, 'wb')) {
54
55		ob_end_clean();
56		// Initialize getID3 engine
57		$getID3 = new getID3;
58		foreach ($FilenamesIn as $nextinputfilename) {
59			$startoffset = 0;
60			$length_seconds      = 0;
61			if (is_array($nextinputfilename)) {
62				@list($nextinputfilename, $startoffset, $length_seconds)  = $nextinputfilename;
63			}
64			$CurrentFileInfo = $getID3->analyze($nextinputfilename);
65			if ($CurrentFileInfo['fileformat'] == 'mp3') {
66
67				ob_start();
68				if ($fp_source = fopen($nextinputfilename, 'rb')) {
69
70					ob_end_clean();
71					$CurrentOutputPosition = ftell($fp_output);
72
73					// copy audio data from first file
74					$start_offset_bytes = $CurrentFileInfo['avdataoffset'];
75					if ($startoffset > 0) {       // start X seconds from start of audio
76						$start_offset_bytes = $CurrentFileInfo['avdataoffset'] + round(($CurrentFileInfo['bitrate'] / 8) * $startoffset);
77					} elseif ($startoffset < 0) { // start X seconds from end of audio
78						$start_offset_bytes = $CurrentFileInfo['avdataend']    + round(($CurrentFileInfo['bitrate'] / 8) * $startoffset);
79					}
80					$start_offset_bytes = max($CurrentFileInfo['avdataoffset'], min($CurrentFileInfo['avdataend'], $start_offset_bytes));
81
82					$end_offset_bytes = $CurrentFileInfo['avdataend'];
83					if ($length_seconds > 0) {       // set end offset to X seconds from start of audio
84						$end_offset_bytes = $start_offset_bytes           + round(($CurrentFileInfo['bitrate'] / 8) * $length_seconds);
85					} elseif ($length_seconds < 0) { // set end offset to X seconds from end of audio
86						$end_offset_bytes = $CurrentFileInfo['avdataend'] + round(($CurrentFileInfo['bitrate'] / 8) * $length_seconds);
87					}
88					$end_offset_bytes = max($CurrentFileInfo['avdataoffset'], min($CurrentFileInfo['avdataend'], $end_offset_bytes));
89
90					if ($end_offset_bytes <= $start_offset_bytes) {
91						echo 'failed to copy '.$nextinputfilename.' from '.$startoffset.'-seconds start for '.$length_seconds.'-seconds length (not enough data)';
92						fclose($fp_source);
93						fclose($fp_output);
94						return false;
95					}
96
97					fseek($fp_source, $start_offset_bytes, SEEK_SET);
98					while (!feof($fp_source) && (ftell($fp_source) < $end_offset_bytes)) {
99						fwrite($fp_output, fread($fp_source, min(32768, $end_offset_bytes - ftell($fp_source))));
100					}
101					fclose($fp_source);
102
103				} else {
104
105					$errormessage = ob_get_contents();
106					ob_end_clean();
107					echo 'failed to open '.$nextinputfilename.' for reading';
108					fclose($fp_output);
109					return false;
110
111				}
112
113			} else {
114
115				echo $nextinputfilename.' is not MP3 format';
116				fclose($fp_output);
117				return false;
118
119			}
120
121		}
122
123	} else {
124
125		$errormessage = ob_get_contents();
126		ob_end_clean();
127		echo 'failed to open '.$FilenameOut.' for writing';
128		return false;
129
130	}
131
132	fclose($fp_output);
133	return true;
134}
135