├── .gitignore ├── src ├── Models │ ├── Audio.php │ ├── Effect.php │ ├── VideoFrame.php │ ├── ImageFrame.php │ ├── Frame.php │ ├── ZoomEffect.php │ ├── FadeEffect.php │ ├── Input.php │ ├── Text.php │ └── Movie.php └── MovieMaker.php ├── composer.json ├── README.md ├── test └── MovieMakerTest.php └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | .idea 3 | /source/ -------------------------------------------------------------------------------- /src/Models/Audio.php: -------------------------------------------------------------------------------- 1 | =7.0.0" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "~4.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Models/VideoFrame.php: -------------------------------------------------------------------------------- 1 | isProcessed() || $force ? 25 | $this->getFilePathExt($movie->getProcessedExtensionVideo(), $movie->getOutputDirectory(), true) : $this->getFilePath(); 26 | } 27 | } -------------------------------------------------------------------------------- /src/Models/ImageFrame.php: -------------------------------------------------------------------------------- 1 | isProcessed() || $force ? 26 | $this->getFilePathExt($movie->getProcessedExtensionImage(), $movie->getOutputDirectory(), true) : $this->getFilePath(); 27 | } 28 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ffmpeg-maker 2 | PHP library allow create simple movie with ffmpeg 3 | 4 | ### Install 5 | 6 | > TODO 7 | 8 | 9 | ### Initialize object 10 | 11 | 12 | Initialize object Movie 13 | 14 | ``` 15 | $mv = new Movie([ 16 | 'width' => 640, 17 | 'height' => 480, 18 | 'outputFile' => 'movie.mp4', 19 | 'outputDirectory' => './out', 20 | ]); 21 | ``` 22 | 23 | Add frame 24 | 25 | ``` 26 | $mv->addFrame(new ImageFrame([ 27 | 'filePath' => 'image1.jpg', 28 | 'duration' => 6, 29 | 'effects' => [ 30 | FadeEffect::makeIn(1, 0), 31 | FadeEffect::makeOut(1, 4) 32 | ], 33 | 'text' => new Text([ 34 | 'value' => "Hello world!\nSome text.", 35 | 'posX' => '10', 36 | 'posY' => '20', 37 | 'color' => '#ffff00', 38 | 'box' => true, 39 | 'wrap' => false, 40 | ]) 41 | ])); 42 | ``` 43 | 44 | Add audio track 45 | 46 | ``` 47 | $mv->addAudio(new Audio([ 48 | 'filePath' => 'audio.mp3', 49 | ])); 50 | ``` 51 | 52 | Build movie 53 | 54 | ``` 55 | $mm = new MovieMaker($mv); 56 | $mm->build(); 57 | ``` 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/Models/Frame.php: -------------------------------------------------------------------------------- 1 | effects; 44 | } 45 | 46 | /** 47 | * @param Effect[] $effects 48 | */ 49 | public function setEffects($effects) 50 | { 51 | $this->effects = $effects; 52 | } 53 | 54 | /** 55 | * @param Effect $effect 56 | */ 57 | public function addEffect(Effect $effect) { 58 | $this->effects[] = $effect; 59 | } 60 | 61 | /** 62 | * @return Text 63 | */ 64 | public function getText() 65 | { 66 | return $this->text; 67 | } 68 | 69 | /** 70 | * @param Text $text 71 | */ 72 | public function setText($text) 73 | { 74 | $this->text = $text; 75 | } 76 | 77 | 78 | /** 79 | * @param bool $processed 80 | */ 81 | public function setProcessed($processed) { 82 | $this->processed = $processed; 83 | } 84 | 85 | 86 | /** 87 | * @return bool 88 | */ 89 | public function isProcessed() { 90 | return $this->processed; 91 | } 92 | 93 | 94 | /** 95 | * @param Movie $movie 96 | * @param bool $force 97 | * @return mixed 98 | */ 99 | abstract public function getProcessedFile(Movie $movie, $force = false); 100 | 101 | 102 | 103 | 104 | 105 | } -------------------------------------------------------------------------------- /src/Models/ZoomEffect.php: -------------------------------------------------------------------------------- 1 | duration = $duration; 35 | $this->fps = $fps; 36 | if(is_null($scale)) { 37 | $scale = '640x480'; 38 | } 39 | 40 | $this->scale = $scale; 41 | } 42 | 43 | 44 | /** 45 | * @return string 46 | */ 47 | public function getCommands() 48 | { 49 | $d = $this->getDuration() * $this->getFps(); 50 | return "zoompan=z='if(lte(zoom,1.0),1.5,max(1.001,zoom-0.0050))':d={$d},trim=duration={$this->getDuration()},scale={$this->getScale()},setdar=dar=4:3"; 51 | } 52 | 53 | /** 54 | * @return int 55 | */ 56 | public function getDuration(): int 57 | { 58 | return $this->duration; 59 | } 60 | 61 | /** 62 | * @param int $duration 63 | */ 64 | public function setDuration(int $duration) 65 | { 66 | $this->duration = $duration; 67 | } 68 | 69 | /** 70 | * @return int 71 | */ 72 | public function getFps(): int 73 | { 74 | return $this->fps; 75 | } 76 | 77 | /** 78 | * @param int $fps 79 | */ 80 | public function setFps(int $fps) 81 | { 82 | $this->fps = $fps; 83 | } 84 | 85 | /** 86 | * @return null|string 87 | */ 88 | public function getScale() 89 | { 90 | return $this->scale; 91 | } 92 | 93 | /** 94 | * @param null|string $scale 95 | */ 96 | public function setScale($scale) 97 | { 98 | $this->scale = $scale; 99 | } 100 | 101 | 102 | 103 | 104 | } -------------------------------------------------------------------------------- /src/Models/FadeEffect.php: -------------------------------------------------------------------------------- 1 | type = $type; 50 | $this->duration = $duration; 51 | $this->startTime = $startTime; 52 | } 53 | 54 | 55 | /** 56 | * @return int 57 | */ 58 | public function getDuration(): int 59 | { 60 | return $this->duration; 61 | } 62 | 63 | /** 64 | * @param int $duration 65 | */ 66 | public function setDuration(int $duration) 67 | { 68 | $this->duration = $duration; 69 | } 70 | 71 | /** 72 | * @return int 73 | */ 74 | public function getStartTime(): int 75 | { 76 | return $this->startTime; 77 | } 78 | 79 | /** 80 | * @param int $startTime 81 | */ 82 | public function setStartTime(int $startTime) 83 | { 84 | $this->startTime = $startTime; 85 | } 86 | 87 | /** 88 | * @return string 89 | */ 90 | public function getType(): string 91 | { 92 | return $this->type; 93 | } 94 | 95 | /** 96 | * @param string $type 97 | */ 98 | public function setType(string $type) 99 | { 100 | $this->type = $type; 101 | } 102 | 103 | 104 | /** 105 | * @return string 106 | */ 107 | public function getCommands() 108 | { 109 | $st = $this->getStartTime(); 110 | return "fade=t={$this->getType()}:st={$st}:d={$this->getDuration()}"; 111 | } 112 | 113 | /** 114 | * @param int $duration 115 | * @param int $startTime 116 | * @return FadeEffect 117 | */ 118 | public static function makeIn($duration = 1, $startTime = 0) { 119 | return new static(static::TYPE_IN, $duration, $startTime); 120 | } 121 | 122 | /** 123 | * @param int $duration 124 | * @param int $startTime 125 | * @return FadeEffect 126 | */ 127 | public static function makeOut($duration = 1, $startTime = 0) { 128 | return new static(static::TYPE_OUT, $duration, $startTime); 129 | } 130 | } -------------------------------------------------------------------------------- /src/Models/Input.php: -------------------------------------------------------------------------------- 1 | $value) { 45 | $this->{$name} = $value; 46 | } 47 | } 48 | 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getFilePath() 54 | { 55 | return $this->filePath; 56 | } 57 | 58 | /** 59 | * @param string $filePath 60 | */ 61 | public function setFilePath($filePath) 62 | { 63 | $this->filePath = $filePath; 64 | } 65 | 66 | 67 | /** 68 | * @return int 69 | */ 70 | public function getDuration() 71 | { 72 | return $this->duration; 73 | } 74 | 75 | /** 76 | * @param int $duration 77 | */ 78 | public function setDuration($duration) 79 | { 80 | $this->duration = $duration; 81 | } 82 | 83 | /** 84 | * @param string $mask 85 | * @return mixed|string 86 | */ 87 | public function getFilePathMask($mask = '%s') { 88 | $file = $this->getFilePath(); 89 | if(preg_match('/.+\/(.+)\.[a-zA-Z]{2,}/', $file, $math) && isset($math[1])) { 90 | return str_replace($math[1], $mask, $file); 91 | } 92 | return $file; 93 | } 94 | 95 | /** 96 | * @param null|string $exc 97 | * @param null|string $outputDir 98 | * @param boolean $modeName 99 | * @return string 100 | */ 101 | public function getFilePathExt($exc = null, $outputDir = null, $modeName = false) { 102 | 103 | $info = pathinfo($this->getFilePath()); 104 | if(empty($outputDir)) { 105 | $outputDir = $info['dirname']; 106 | } 107 | if(empty($exc)) { 108 | $exc = $info['extension']; 109 | } 110 | $filename = $info['filename']; 111 | if($modeName) { 112 | $filename .= $this->getName(); 113 | } 114 | return rtrim($outputDir, '/') .DIRECTORY_SEPARATOR . $filename . '.' .$exc; 115 | } 116 | 117 | /** 118 | * @return string 119 | */ 120 | public function getName() 121 | { 122 | if(empty($this->name)) { 123 | $this->name = sprintf("f_%d", $this->getUnique()); 124 | } 125 | return $this->name; 126 | } 127 | 128 | /** 129 | * @param string $name 130 | */ 131 | public function setName($name) 132 | { 133 | $this->name = $name; 134 | } 135 | 136 | 137 | /** 138 | * @return int 139 | */ 140 | protected function getUnique() { 141 | return ++ self::$index; 142 | } 143 | 144 | 145 | /** 146 | * @return string 147 | */ 148 | public function getClassName() { 149 | return static::class; 150 | } 151 | 152 | 153 | } -------------------------------------------------------------------------------- /src/Models/Text.php: -------------------------------------------------------------------------------- 1 | $value) { 83 | $this->{$name} = $value; 84 | } 85 | } 86 | 87 | 88 | /** 89 | * @return string 90 | */ 91 | public function getValue() 92 | { 93 | return $this->value; 94 | } 95 | 96 | /** 97 | * @param string $value 98 | */ 99 | public function setValue($value) 100 | { 101 | $this->value = $value; 102 | } 103 | 104 | /** 105 | * @return string 106 | */ 107 | public function getPosX() 108 | { 109 | return $this->posX; 110 | } 111 | 112 | /** 113 | * @param string $posX 114 | */ 115 | public function setPosX($posX) 116 | { 117 | $this->posX = $posX; 118 | } 119 | 120 | /** 121 | * @return string 122 | */ 123 | public function getPosY() 124 | { 125 | return $this->posY; 126 | } 127 | 128 | /** 129 | * @param string $posY 130 | */ 131 | public function setPosY($posY) 132 | { 133 | $this->posY = $posY; 134 | } 135 | 136 | /** 137 | * @return string 138 | */ 139 | public function getColor() 140 | { 141 | return $this->color; 142 | } 143 | 144 | /** 145 | * @param string $color 146 | */ 147 | public function setColor($color) 148 | { 149 | $this->color = $color; 150 | } 151 | 152 | /** 153 | * @return string 154 | */ 155 | public function getFontLink() 156 | { 157 | return $this->fontLink; 158 | } 159 | 160 | /** 161 | * @param string $fontLink 162 | */ 163 | public function setFontLink($fontLink) 164 | { 165 | $this->fontLink = $fontLink; 166 | } 167 | 168 | /** 169 | * @return int 170 | */ 171 | public function getFontSize() 172 | { 173 | return $this->fontSize; 174 | } 175 | 176 | /** 177 | * @param int $fontSize 178 | */ 179 | public function setFontSize($fontSize) 180 | { 181 | $this->fontSize = $fontSize; 182 | } 183 | 184 | /** 185 | * @return bool 186 | */ 187 | public function isWrap(): bool 188 | { 189 | return $this->wrap; 190 | } 191 | 192 | /** 193 | * @param bool $wrap 194 | */ 195 | public function setWrap(bool $wrap) 196 | { 197 | $this->wrap = $wrap; 198 | } 199 | 200 | /** 201 | * @return bool 202 | */ 203 | public function isBox(): bool 204 | { 205 | return $this->box; 206 | } 207 | 208 | /** 209 | * @param bool $box 210 | */ 211 | public function setBox(bool $box) 212 | { 213 | $this->box = $box; 214 | } 215 | 216 | /** 217 | * @return string 218 | */ 219 | public function getBoxColor(): string 220 | { 221 | return $this->boxColor; 222 | } 223 | 224 | /** 225 | * @param string $boxColor 226 | */ 227 | public function setBoxColor(string $boxColor) 228 | { 229 | $this->boxColor = $boxColor; 230 | } 231 | 232 | 233 | /** 234 | * @param int $horizontal 235 | * @param int $vertical 236 | * @see TEXT_POSITION_* 237 | */ 238 | public function setTextPosition($horizontal, $vertical) { 239 | $margin = self::TEXT_MARGIN; 240 | 241 | $x = "(w * {$margin})";//LEFT 242 | $y = "(h * {$margin})";//TOP 243 | 244 | if($horizontal == self::TEXT_POSITION_CENTER) { 245 | $x = "(w / 2 - text_w / 2)"; 246 | } else if($horizontal == self::TEXT_POSITION_RIGHT) { 247 | $x = "((w - text_w) - (w * {$margin}))"; 248 | } 249 | 250 | if($vertical == self::TEXT_POSITION_CENTER) { 251 | $y = "(h / 2 - text_h / 2)"; 252 | } else if($vertical == self::TEXT_POSITION_BOTTOM) { 253 | $y = "((h - text_h) - (h * {$margin}))"; 254 | } 255 | 256 | $this->setPosX($x); 257 | $this->setPosY($y); 258 | } 259 | 260 | 261 | } -------------------------------------------------------------------------------- /src/Models/Movie.php: -------------------------------------------------------------------------------- 1 | $value) { 64 | $this->{$name} = $value; 65 | } 66 | } 67 | 68 | /** 69 | * @return Frame[] 70 | */ 71 | public function getFrames() 72 | { 73 | return $this->frames; 74 | } 75 | 76 | /** 77 | * @return int 78 | */ 79 | public function getFrameCount() { 80 | return count($this->getFrames()); 81 | } 82 | 83 | /** 84 | * @param Frame[] $frames 85 | */ 86 | public function setFrames($frames) 87 | { 88 | $this->frames = $frames; 89 | } 90 | 91 | /** 92 | * @return int 93 | */ 94 | public function getWidth() 95 | { 96 | return $this->width; 97 | } 98 | 99 | /** 100 | * @param int $width 101 | */ 102 | public function setWidth($width) 103 | { 104 | $this->width = $width; 105 | } 106 | 107 | /** 108 | * @return int 109 | */ 110 | public function getHeight() 111 | { 112 | return $this->height; 113 | } 114 | 115 | /** 116 | * @param int $height 117 | */ 118 | public function setHeight($height) 119 | { 120 | $this->height = $height; 121 | } 122 | 123 | /** 124 | * @param string $size 125 | */ 126 | public function setSizeStr($size) { 127 | if(!empty($size) && preg_match('/[0-9]+x[0-9]+/i', $size)) { 128 | list($width, $height) = explode('x', $size); 129 | $this->setWidth($width); 130 | $this->setHeight($height); 131 | } 132 | } 133 | 134 | /** 135 | * @return string 136 | */ 137 | public function getSizeStr() { 138 | return sprintf("%dx%d", $this->getWidth(), $this->getHeight()); 139 | } 140 | 141 | /** 142 | * @return Audio[] 143 | */ 144 | public function getAudioTracks() 145 | { 146 | return $this->audioTracks; 147 | } 148 | 149 | /** 150 | * @param Audio[] $audioTracks 151 | */ 152 | public function setAudioTracks($audioTracks) 153 | { 154 | $this->audioTracks = $audioTracks; 155 | } 156 | 157 | 158 | /** 159 | * @return int 160 | */ 161 | public function getDuration() { 162 | $duration = 0; 163 | foreach ($this->frames as $frame) { 164 | $duration += $frame->getDuration(); 165 | } 166 | return $duration; 167 | } 168 | 169 | /** 170 | * @param Frame $frame 171 | */ 172 | public function addFrame(Frame $frame) { 173 | $this->frames[] = $frame; 174 | } 175 | 176 | 177 | /** 178 | * @param Audio $audio 179 | */ 180 | public function addAudio(Audio $audio) { 181 | $this->audioTracks[] = $audio; 182 | } 183 | 184 | /** 185 | * @return bool 186 | */ 187 | public function needResize() { 188 | return !empty($this->getHeight()) && !empty($this->getWidth()); 189 | } 190 | 191 | /** 192 | * @return string 193 | */ 194 | public function getProcessedExtensionImage(): string 195 | { 196 | return $this->processedExtensionImage; 197 | } 198 | 199 | /** 200 | * @param string processedExtensionImage 201 | */ 202 | public function setProcessedExtensionImage(string $processedExtensionImage) 203 | { 204 | $this->processedExtensionImage = $processedExtensionImage; 205 | } 206 | 207 | /** 208 | * @return string 209 | */ 210 | public function getOutputFile(): string 211 | { 212 | return $this->outputFile; 213 | } 214 | 215 | /** 216 | * @param string $outputFile 217 | */ 218 | public function setOutputFile(string $outputFile) 219 | { 220 | $this->outputFile = $outputFile; 221 | } 222 | 223 | /** 224 | * @return string 225 | */ 226 | public function getProcessedExtensionVideo(): string 227 | { 228 | return $this->processedExtensionVideo; 229 | } 230 | 231 | /** 232 | * @param string $processedExtensionVideo 233 | */ 234 | public function setProcessedExtensionVideo(string $processedExtensionVideo) 235 | { 236 | $this->processedExtensionVideo = $processedExtensionVideo; 237 | } 238 | 239 | /** 240 | * @return string 241 | */ 242 | public function getOutputDirectory(): string 243 | { 244 | return $this->outputDirectory; 245 | } 246 | 247 | /** 248 | * @param string $outputDirectory 249 | */ 250 | public function setOutputDirectory(string $outputDirectory) 251 | { 252 | $this->outputDirectory = $outputDirectory; 253 | } 254 | 255 | 256 | 257 | 258 | 259 | 260 | } -------------------------------------------------------------------------------- /test/MovieMakerTest.php: -------------------------------------------------------------------------------- 1 | 640, 38 | 'height' => 480, 39 | 'outputFile' => $this->getSourcePath() . 'out/movie.mp4', 40 | 'outputDirectory' => $this->getSourcePath() . 'out', 41 | ]); 42 | 43 | $mv->addAudio(new Audio([ 44 | 'filePath' => $this->getSourcePath() . 'sone.mp3', 45 | ])); 46 | 47 | $mv->addFrame(new ImageFrame([ 48 | 'filePath' => $this->getSourcePath() . 'image1.jpg', 49 | 'duration' => 6, 50 | 'effects' => [ 51 | FadeEffect::makeIn(1, 0), 52 | FadeEffect::makeOut(1, 4) 53 | ], 54 | 'text' => new Text([ 55 | 'value' => "Hello world!\nxsxsxsxs 56 | xsxsx 57 | xsxs", 58 | 'posX' => '10', 59 | 'posY' => '20', 60 | 'color' => '#ffff00', 61 | 'box' => true, 62 | 'wrap' => false, 63 | ]) 64 | ])); 65 | $mv->addFrame(new ImageFrame([ 66 | 'filePath' => $this->getSourcePath() . 'image3.jpg', 67 | 'duration' => 2, 68 | 'effects' => [ 69 | FadeEffect::makeIn(1, 0), 70 | FadeEffect::makeOut(1, 4) 71 | ], 72 | 'text' => new Text([ 73 | 'value' => 'Привет мир!', 74 | ]) 75 | ])); 76 | $mv->addFrame(new ImageFrame([ 77 | 'filePath' => $this->getSourcePath() . 'image2.jpg', 78 | 'duration' => 10, 79 | 'effects' => [ 80 | FadeEffect::makeIn(1, 0) 81 | ], 82 | 'text' => new Text([ 83 | 'value' => 'Привет мир! Привет мир! Привет мир! '. "\n". 84 | '^LПривет мир! Привет мир! Привет мир! Привет мир! Привет мир!' . "\n". 85 | 'Привет мир! Привет мир! Привет мир! ^LПривет мир! Привет мир! Привет мир! Привет мир! Привет мир!' . "\n". 86 | 'Привет мир! Привет мир! Привет мир! Привет мир! Привет мир! Привет мир! Привет мир! Привет мир!' 87 | , 88 | 'posX' => '10', 89 | 'posY' => '20', 90 | 'color' => '#b642f4', 91 | 'box' => true, 92 | 'boxColor' => '#ffffff', 93 | ]) 94 | ])); 95 | $mv->addFrame(new ImageFrame([ 96 | 'filePath' => $this->getSourcePath() . 'image4.jpg', 97 | 'duration' => 3, 98 | 'effects' => [ 99 | FadeEffect::makeIn(1, 0) 100 | ], 101 | ])); 102 | $mv->addFrame(new ImageFrame([ 103 | 'filePath' => $this->getSourcePath() . 'image5.jpg', 104 | 'duration' => 3, 105 | 'effects' => [ 106 | FadeEffect::makeIn(1, 0) 107 | ], 108 | ])); 109 | 110 | $mm = new MovieMaker($mv); 111 | $mm->build(); 112 | 113 | 114 | } 115 | 116 | /** 117 | * 118 | */ 119 | public function testMerge() { 120 | 121 | $mv = new Movie([ 122 | 'width' => 640, 123 | 'height' => 480, 124 | 'outputFile' => $this->getSourcePath() . 'out/join.mp4', 125 | 'outputDirectory' => $this->getSourcePath() . 'out', 126 | ]); 127 | 128 | $mv->addFrame(new ImageFrame([ 129 | 'filePath' => $this->getSourcePath() . 'german3.jpg', 130 | 'duration' => 4, 131 | 'effects' => [ 132 | FadeEffect::makeIn(1, 0) 133 | ], 134 | 'text' => new Text([ 135 | 'value' => "Hello world!\n 136 | Привет мир!", 137 | 'posX' => '10', 138 | 'posY' => '20', 139 | 'color' => '#000000', 140 | 'box' => true, 141 | 'wrap' => false, 142 | ]) 143 | ])); 144 | 145 | $mv->addFrame(new ImageFrame([ 146 | 'filePath' => $this->getSourcePath() . 'german4.jpg', 147 | 'duration' => 3, 148 | 'effects' => [ 149 | FadeEffect::makeIn(1, 0) 150 | ], 151 | ])); 152 | $mv->addFrame(new ImageFrame([ 153 | 'filePath' => $this->getSourcePath() . 'image3.jpg', 154 | 'duration' => 3, 155 | 'effects' => [ 156 | FadeEffect::makeIn(1, 0) 157 | ], 158 | ])); 159 | 160 | $mv->addFrame(new VideoFrame([ 161 | 'filePath' => $this->getSourcePath() . 'videoplayback.mp4', 162 | 'duration' => 10, 163 | 'allowProcessed' => false, 164 | 'effects' => [ 165 | FadeEffect::makeIn(1, 0) 166 | ], 167 | ])); 168 | $mv->addFrame(new ImageFrame([ 169 | 'filePath' => $this->getSourcePath() . 'image4.jpg', 170 | 'duration' => 10, 171 | 'effects' => [ 172 | new ZoomEffect(10) 173 | ], 174 | ])); 175 | 176 | $mv->addAudio(new Audio([ 177 | 'filePath' => $this->getSourcePath() . 'sone.mp3', 178 | 'duration' => 30, 179 | ])); /* */ 180 | 181 | 182 | 183 | 184 | 185 | $mm = new MovieMaker($mv); 186 | $mm->build(); 187 | 188 | } 189 | } -------------------------------------------------------------------------------- /src/MovieMaker.php: -------------------------------------------------------------------------------- 1 | movie = $movie; 36 | } 37 | 38 | /** 39 | * @return Movie|null 40 | */ 41 | public function getMovie() { 42 | return $this->movie; 43 | } 44 | 45 | 46 | /** 47 | * 48 | */ 49 | public function build() { 50 | $movie = $this->getMovie(); 51 | foreach ($movie->getFrames() as $frame) { 52 | $this->processedCommand($frame); 53 | } 54 | 55 | $this->buildMove(); 56 | $this->clean(); 57 | } 58 | 59 | 60 | /** 61 | * 62 | */ 63 | public function clean() { 64 | $movie = $this->getMovie(); 65 | $scan = scandir( $movie->getOutputDirectory() ); 66 | 67 | foreach ($scan as $file) { 68 | if(preg_match('/f_[0-9]+\./', $file)) { 69 | $this->exec("rm {$movie->getOutputDirectory()}/{$file}"); 70 | } 71 | } 72 | } 73 | 74 | 75 | /** 76 | * @param $outputFile 77 | * @return null|string 78 | */ 79 | private function getAddAudioCommand($outputFile){ 80 | $movie = $this->getMovie(); 81 | if(empty($movie->getAudioTracks())) { 82 | return null; 83 | } 84 | 85 | $buffer = self::PROGRAM_NAME; 86 | $totalDuration = $movie->getDuration(); 87 | 88 | $buffer .= " -i {$outputFile} "; 89 | foreach ($movie->getAudioTracks() as $audio) { 90 | $duration = $audio->getDuration() ? $audio->getDuration() : $totalDuration; 91 | $buffer .= " -t {$duration} -i {$audio->getFilePath()} "; 92 | } 93 | 94 | $buffer .= "-c copy -map 0:v:0 -map 1:a:0 -y {$movie->getOutputFile()}"; 95 | 96 | return $buffer; 97 | } 98 | 99 | 100 | /** 101 | * 102 | */ 103 | private function buildMove(){ 104 | 105 | $movie = $this->getMovie(); 106 | $movies = []; 107 | $images = []; 108 | 109 | foreach ($movie->getFrames() as $frame) { 110 | 111 | if($frame instanceof VideoFrame) { 112 | if(!empty($images)) { 113 | $movies[] = $this->imagesToVideo($images); 114 | $images = []; 115 | } 116 | 117 | $movies[] = $frame; 118 | continue; 119 | } 120 | 121 | $images[] = $frame; 122 | } 123 | 124 | if(!empty($images)) { 125 | $movies[] = $this->imagesToVideo($images); 126 | } 127 | 128 | $command = self::PROGRAM_NAME; 129 | $video = new VideoFrame([ 130 | 'filePath' => $movie->getOutputFile(), 131 | ]); 132 | $outputFile = $video->getFilePathExt(null, null, true); 133 | 134 | $moviesStr = join('|', array_map(function($m) use(&$movie){ return $m->getProcessedFile($movie); }, $movies)); 135 | // -f mpegts 136 | $command .= " -i 'concat:{$moviesStr}' -y {$outputFile}"; //-c copy -bsf:a yuv420p 137 | $this->exec($command); 138 | 139 | //add audio 140 | if($command = $this->getAddAudioCommand($outputFile)) { 141 | $this->exec($command); 142 | } else { 143 | $this->exec("mv {$outputFile} {$movie->getOutputFile()}"); 144 | } 145 | } 146 | 147 | 148 | /** 149 | * @param ImageFrame[] $frames 150 | * @return VideoFrame 151 | */ 152 | private function imagesToVideo($frames) { 153 | $command = self::PROGRAM_NAME; 154 | $effects = []; 155 | $count = 0; 156 | 157 | $movie = $this->getMovie(); 158 | 159 | foreach ($frames as $n => $frame) { 160 | 161 | $command .= " -loop 1 -t {$frame->getDuration()} -i {$frame->getProcessedFile($movie)} "; 162 | $efs = []; 163 | foreach($frame->getEffects() as $ef) { 164 | $efs[] = $ef->getCommands(); 165 | } 166 | if(empty($efs)) { 167 | //$n = count($effects); 168 | $efs[] = "trim=duration={$frame->getDuration()}"; 169 | } 170 | $effects['[v' . $n.']'] = '['.$n.':v]' . join(',', $efs) .'[v'.$n.'];'; 171 | } 172 | 173 | $command .= " -filter_complex \""; 174 | 175 | if(!empty($effects)) { 176 | $command .= join('', $effects) . ' '; 177 | $command .= join('', array_keys($effects)); 178 | } 179 | //TODO format=yuv420p to vars 180 | $count = count($frames); 181 | $command .= "concat=n={$count}:v=1:a=0,format=yuv420p[v]\" "; 182 | 183 | $video = new VideoFrame([ 184 | 'filePath' => $movie->getOutputFile(), 185 | ]); 186 | 187 | $command .= '-map "[v]" -vb 20M '; 188 | $command .= "-y {$video->getProcessedFile($movie, true)}"; 189 | 190 | $this->exec($command); 191 | $video->setProcessed(true); 192 | 193 | return $video; 194 | } 195 | 196 | /** 197 | * @param string $command 198 | * @throws \Exception 199 | */ 200 | private function exec($command) { 201 | 202 | echo $command, PHP_EOL; 203 | 204 | $ret = null; 205 | $cmd = system($command, $ret); 206 | 207 | if($ret !== 0) { 208 | throw new \Exception("Returned an error: {$cmd}, command: {$command}, {$ret}"); 209 | } 210 | } 211 | 212 | /** 213 | * @param Frame $frame 214 | */ 215 | private function processedCommand(Frame $frame) { 216 | if(!empty($command = $this->getProcessedCommand($frame))) { 217 | $this->exec($command); 218 | $frame->setProcessed(true); 219 | } 220 | } 221 | 222 | /** 223 | * @param Frame $frame 224 | * @return string 225 | */ 226 | private function getProcessedCommand(Frame $frame) { 227 | 228 | switch ($frame->getClassName()) { 229 | case VideoFrame::class: 230 | return $this->getProcessedCommandVideo($frame); 231 | case ImageFrame::class: 232 | return $this->getProcessedCommandImage($frame); 233 | } 234 | 235 | return ''; 236 | } 237 | 238 | /** 239 | * @param VideoFrame $frame 240 | * @return string 241 | */ 242 | private function getProcessedCommandVideo(VideoFrame $frame) { 243 | $movie = $this->getMovie(); 244 | $filters = []; 245 | if($movie->needResize()) { 246 | $filters[] = "scale={$movie->getWidth()}:{$movie->getHeight()}"; 247 | } 248 | $duration = $frame->getDuration() > 0 ? $frame->getDuration() : 1; 249 | $command = MovieMaker::PROGRAM_NAME; 250 | if($duration) { 251 | $command .= " -t {$duration} "; 252 | } 253 | $command .= " -i {$frame->getFilePath()} "; 254 | $command .= '-vf "'. join(',', $filters) .'" '; 255 | //TODO to config 256 | //$command .= ' -bsf:v h264_mp4toannexb -f mpegts '; 257 | 258 | $command .= "-y {$frame->getProcessedFile($movie, true)}"; 259 | return $command; 260 | } 261 | 262 | /** 263 | * @param ImageFrame $frame 264 | * @return string 265 | */ 266 | private function getProcessedCommandImage(ImageFrame $frame) { 267 | $movie = $this->getMovie(); 268 | //TODO ['setsar=1:1', 'setdar=4:3'] to options 269 | $filters = ['setsar=1:1', 'setdar=4:3']; 270 | if($movie->needResize()) { 271 | $filters[] = "scale={$movie->getWidth()}:{$movie->getHeight()}"; 272 | } 273 | 274 | if(!is_null($text = $frame->getText())) { 275 | $values = $text->isWrap() ? explode("\n", $text->getValue()) : [$text->getValue()]; 276 | $posY = $text->getPosY(); 277 | foreach ($values as $value) { 278 | $value = trim($value); 279 | $fontfile = $text->getFontLink() ? "fontfile={$text->getFontLink()}:" : ''; 280 | $fontcolor = $text->getColor() ? ":fontcolor={$text->getColor()}" : ''; 281 | $fontsize = $text->getColor() ? ":fontsize={$text->getFontSize()}" : ''; 282 | $x = $text->getPosX() ? ":x={$text->getPosX()}" : ''; 283 | $y = $posY ? ":y={$posY}" : ''; 284 | $box = $text->isBox() ? ":box=1:boxcolor={$text->getBoxColor()}:boxborderw=5" : ''; 285 | $drawtext = "drawtext={$fontfile}text='{$value}'{$fontcolor}{$fontsize}{$x}{$y}{$box}"; 286 | $filters[] = $drawtext; 287 | $posY += ($text->getFontSize() ? $text->getFontSize() : 0) + 15; 288 | } 289 | } 290 | 291 | if(empty($filters)) { 292 | return null; 293 | } 294 | 295 | $command = MovieMaker::PROGRAM_NAME; 296 | $command .= " -i {$frame->getFilePath()} "; 297 | $command .= '-vf "'. join(',', $filters) .'" '; 298 | $command .= "-y {$frame->getProcessedFile($movie, true)}"; 299 | 300 | return $command; 301 | } 302 | 303 | } -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "7cc02263222d221f048bafdcb67c01f2", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "doctrine/instantiator", 12 | "version": "1.0.5", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/doctrine/instantiator.git", 16 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", 21 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.3,<8.0-DEV" 26 | }, 27 | "require-dev": { 28 | "athletic/athletic": "~0.1.8", 29 | "ext-pdo": "*", 30 | "ext-phar": "*", 31 | "phpunit/phpunit": "~4.0", 32 | "squizlabs/php_codesniffer": "~2.0" 33 | }, 34 | "type": "library", 35 | "extra": { 36 | "branch-alias": { 37 | "dev-master": "1.0.x-dev" 38 | } 39 | }, 40 | "autoload": { 41 | "psr-4": { 42 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 43 | } 44 | }, 45 | "notification-url": "https://packagist.org/downloads/", 46 | "license": [ 47 | "MIT" 48 | ], 49 | "authors": [ 50 | { 51 | "name": "Marco Pivetta", 52 | "email": "ocramius@gmail.com", 53 | "homepage": "http://ocramius.github.com/" 54 | } 55 | ], 56 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 57 | "homepage": "https://github.com/doctrine/instantiator", 58 | "keywords": [ 59 | "constructor", 60 | "instantiate" 61 | ], 62 | "time": "2015-06-14T21:17:01+00:00" 63 | }, 64 | { 65 | "name": "phpdocumentor/reflection-common", 66 | "version": "1.0.1", 67 | "source": { 68 | "type": "git", 69 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 70 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" 71 | }, 72 | "dist": { 73 | "type": "zip", 74 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", 75 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", 76 | "shasum": "" 77 | }, 78 | "require": { 79 | "php": ">=5.5" 80 | }, 81 | "require-dev": { 82 | "phpunit/phpunit": "^4.6" 83 | }, 84 | "type": "library", 85 | "extra": { 86 | "branch-alias": { 87 | "dev-master": "1.0.x-dev" 88 | } 89 | }, 90 | "autoload": { 91 | "psr-4": { 92 | "phpDocumentor\\Reflection\\": [ 93 | "src" 94 | ] 95 | } 96 | }, 97 | "notification-url": "https://packagist.org/downloads/", 98 | "license": [ 99 | "MIT" 100 | ], 101 | "authors": [ 102 | { 103 | "name": "Jaap van Otterdijk", 104 | "email": "opensource@ijaap.nl" 105 | } 106 | ], 107 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 108 | "homepage": "http://www.phpdoc.org", 109 | "keywords": [ 110 | "FQSEN", 111 | "phpDocumentor", 112 | "phpdoc", 113 | "reflection", 114 | "static analysis" 115 | ], 116 | "time": "2017-09-11T18:02:19+00:00" 117 | }, 118 | { 119 | "name": "phpdocumentor/reflection-docblock", 120 | "version": "4.1.1", 121 | "source": { 122 | "type": "git", 123 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 124 | "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2" 125 | }, 126 | "dist": { 127 | "type": "zip", 128 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/2d3d238c433cf69caeb4842e97a3223a116f94b2", 129 | "reference": "2d3d238c433cf69caeb4842e97a3223a116f94b2", 130 | "shasum": "" 131 | }, 132 | "require": { 133 | "php": "^7.0", 134 | "phpdocumentor/reflection-common": "^1.0@dev", 135 | "phpdocumentor/type-resolver": "^0.4.0", 136 | "webmozart/assert": "^1.0" 137 | }, 138 | "require-dev": { 139 | "mockery/mockery": "^0.9.4", 140 | "phpunit/phpunit": "^4.4" 141 | }, 142 | "type": "library", 143 | "autoload": { 144 | "psr-4": { 145 | "phpDocumentor\\Reflection\\": [ 146 | "src/" 147 | ] 148 | } 149 | }, 150 | "notification-url": "https://packagist.org/downloads/", 151 | "license": [ 152 | "MIT" 153 | ], 154 | "authors": [ 155 | { 156 | "name": "Mike van Riel", 157 | "email": "me@mikevanriel.com" 158 | } 159 | ], 160 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 161 | "time": "2017-08-30T18:51:59+00:00" 162 | }, 163 | { 164 | "name": "phpdocumentor/type-resolver", 165 | "version": "0.4.0", 166 | "source": { 167 | "type": "git", 168 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 169 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" 170 | }, 171 | "dist": { 172 | "type": "zip", 173 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", 174 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", 175 | "shasum": "" 176 | }, 177 | "require": { 178 | "php": "^5.5 || ^7.0", 179 | "phpdocumentor/reflection-common": "^1.0" 180 | }, 181 | "require-dev": { 182 | "mockery/mockery": "^0.9.4", 183 | "phpunit/phpunit": "^5.2||^4.8.24" 184 | }, 185 | "type": "library", 186 | "extra": { 187 | "branch-alias": { 188 | "dev-master": "1.0.x-dev" 189 | } 190 | }, 191 | "autoload": { 192 | "psr-4": { 193 | "phpDocumentor\\Reflection\\": [ 194 | "src/" 195 | ] 196 | } 197 | }, 198 | "notification-url": "https://packagist.org/downloads/", 199 | "license": [ 200 | "MIT" 201 | ], 202 | "authors": [ 203 | { 204 | "name": "Mike van Riel", 205 | "email": "me@mikevanriel.com" 206 | } 207 | ], 208 | "time": "2017-07-14T14:27:02+00:00" 209 | }, 210 | { 211 | "name": "phpspec/prophecy", 212 | "version": "v1.7.2", 213 | "source": { 214 | "type": "git", 215 | "url": "https://github.com/phpspec/prophecy.git", 216 | "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6" 217 | }, 218 | "dist": { 219 | "type": "zip", 220 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", 221 | "reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6", 222 | "shasum": "" 223 | }, 224 | "require": { 225 | "doctrine/instantiator": "^1.0.2", 226 | "php": "^5.3|^7.0", 227 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", 228 | "sebastian/comparator": "^1.1|^2.0", 229 | "sebastian/recursion-context": "^1.0|^2.0|^3.0" 230 | }, 231 | "require-dev": { 232 | "phpspec/phpspec": "^2.5|^3.2", 233 | "phpunit/phpunit": "^4.8 || ^5.6.5" 234 | }, 235 | "type": "library", 236 | "extra": { 237 | "branch-alias": { 238 | "dev-master": "1.7.x-dev" 239 | } 240 | }, 241 | "autoload": { 242 | "psr-0": { 243 | "Prophecy\\": "src/" 244 | } 245 | }, 246 | "notification-url": "https://packagist.org/downloads/", 247 | "license": [ 248 | "MIT" 249 | ], 250 | "authors": [ 251 | { 252 | "name": "Konstantin Kudryashov", 253 | "email": "ever.zet@gmail.com", 254 | "homepage": "http://everzet.com" 255 | }, 256 | { 257 | "name": "Marcello Duarte", 258 | "email": "marcello.duarte@gmail.com" 259 | } 260 | ], 261 | "description": "Highly opinionated mocking framework for PHP 5.3+", 262 | "homepage": "https://github.com/phpspec/prophecy", 263 | "keywords": [ 264 | "Double", 265 | "Dummy", 266 | "fake", 267 | "mock", 268 | "spy", 269 | "stub" 270 | ], 271 | "time": "2017-09-04T11:05:03+00:00" 272 | }, 273 | { 274 | "name": "phpunit/php-code-coverage", 275 | "version": "2.2.4", 276 | "source": { 277 | "type": "git", 278 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 279 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" 280 | }, 281 | "dist": { 282 | "type": "zip", 283 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", 284 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", 285 | "shasum": "" 286 | }, 287 | "require": { 288 | "php": ">=5.3.3", 289 | "phpunit/php-file-iterator": "~1.3", 290 | "phpunit/php-text-template": "~1.2", 291 | "phpunit/php-token-stream": "~1.3", 292 | "sebastian/environment": "^1.3.2", 293 | "sebastian/version": "~1.0" 294 | }, 295 | "require-dev": { 296 | "ext-xdebug": ">=2.1.4", 297 | "phpunit/phpunit": "~4" 298 | }, 299 | "suggest": { 300 | "ext-dom": "*", 301 | "ext-xdebug": ">=2.2.1", 302 | "ext-xmlwriter": "*" 303 | }, 304 | "type": "library", 305 | "extra": { 306 | "branch-alias": { 307 | "dev-master": "2.2.x-dev" 308 | } 309 | }, 310 | "autoload": { 311 | "classmap": [ 312 | "src/" 313 | ] 314 | }, 315 | "notification-url": "https://packagist.org/downloads/", 316 | "license": [ 317 | "BSD-3-Clause" 318 | ], 319 | "authors": [ 320 | { 321 | "name": "Sebastian Bergmann", 322 | "email": "sb@sebastian-bergmann.de", 323 | "role": "lead" 324 | } 325 | ], 326 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 327 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 328 | "keywords": [ 329 | "coverage", 330 | "testing", 331 | "xunit" 332 | ], 333 | "time": "2015-10-06T15:47:00+00:00" 334 | }, 335 | { 336 | "name": "phpunit/php-file-iterator", 337 | "version": "1.4.2", 338 | "source": { 339 | "type": "git", 340 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 341 | "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" 342 | }, 343 | "dist": { 344 | "type": "zip", 345 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5", 346 | "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", 347 | "shasum": "" 348 | }, 349 | "require": { 350 | "php": ">=5.3.3" 351 | }, 352 | "type": "library", 353 | "extra": { 354 | "branch-alias": { 355 | "dev-master": "1.4.x-dev" 356 | } 357 | }, 358 | "autoload": { 359 | "classmap": [ 360 | "src/" 361 | ] 362 | }, 363 | "notification-url": "https://packagist.org/downloads/", 364 | "license": [ 365 | "BSD-3-Clause" 366 | ], 367 | "authors": [ 368 | { 369 | "name": "Sebastian Bergmann", 370 | "email": "sb@sebastian-bergmann.de", 371 | "role": "lead" 372 | } 373 | ], 374 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 375 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 376 | "keywords": [ 377 | "filesystem", 378 | "iterator" 379 | ], 380 | "time": "2016-10-03T07:40:28+00:00" 381 | }, 382 | { 383 | "name": "phpunit/php-text-template", 384 | "version": "1.2.1", 385 | "source": { 386 | "type": "git", 387 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 388 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 389 | }, 390 | "dist": { 391 | "type": "zip", 392 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 393 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 394 | "shasum": "" 395 | }, 396 | "require": { 397 | "php": ">=5.3.3" 398 | }, 399 | "type": "library", 400 | "autoload": { 401 | "classmap": [ 402 | "src/" 403 | ] 404 | }, 405 | "notification-url": "https://packagist.org/downloads/", 406 | "license": [ 407 | "BSD-3-Clause" 408 | ], 409 | "authors": [ 410 | { 411 | "name": "Sebastian Bergmann", 412 | "email": "sebastian@phpunit.de", 413 | "role": "lead" 414 | } 415 | ], 416 | "description": "Simple template engine.", 417 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 418 | "keywords": [ 419 | "template" 420 | ], 421 | "time": "2015-06-21T13:50:34+00:00" 422 | }, 423 | { 424 | "name": "phpunit/php-timer", 425 | "version": "1.0.9", 426 | "source": { 427 | "type": "git", 428 | "url": "https://github.com/sebastianbergmann/php-timer.git", 429 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" 430 | }, 431 | "dist": { 432 | "type": "zip", 433 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", 434 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", 435 | "shasum": "" 436 | }, 437 | "require": { 438 | "php": "^5.3.3 || ^7.0" 439 | }, 440 | "require-dev": { 441 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" 442 | }, 443 | "type": "library", 444 | "extra": { 445 | "branch-alias": { 446 | "dev-master": "1.0-dev" 447 | } 448 | }, 449 | "autoload": { 450 | "classmap": [ 451 | "src/" 452 | ] 453 | }, 454 | "notification-url": "https://packagist.org/downloads/", 455 | "license": [ 456 | "BSD-3-Clause" 457 | ], 458 | "authors": [ 459 | { 460 | "name": "Sebastian Bergmann", 461 | "email": "sb@sebastian-bergmann.de", 462 | "role": "lead" 463 | } 464 | ], 465 | "description": "Utility class for timing", 466 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 467 | "keywords": [ 468 | "timer" 469 | ], 470 | "time": "2017-02-26T11:10:40+00:00" 471 | }, 472 | { 473 | "name": "phpunit/php-token-stream", 474 | "version": "1.4.11", 475 | "source": { 476 | "type": "git", 477 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 478 | "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" 479 | }, 480 | "dist": { 481 | "type": "zip", 482 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7", 483 | "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", 484 | "shasum": "" 485 | }, 486 | "require": { 487 | "ext-tokenizer": "*", 488 | "php": ">=5.3.3" 489 | }, 490 | "require-dev": { 491 | "phpunit/phpunit": "~4.2" 492 | }, 493 | "type": "library", 494 | "extra": { 495 | "branch-alias": { 496 | "dev-master": "1.4-dev" 497 | } 498 | }, 499 | "autoload": { 500 | "classmap": [ 501 | "src/" 502 | ] 503 | }, 504 | "notification-url": "https://packagist.org/downloads/", 505 | "license": [ 506 | "BSD-3-Clause" 507 | ], 508 | "authors": [ 509 | { 510 | "name": "Sebastian Bergmann", 511 | "email": "sebastian@phpunit.de" 512 | } 513 | ], 514 | "description": "Wrapper around PHP's tokenizer extension.", 515 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 516 | "keywords": [ 517 | "tokenizer" 518 | ], 519 | "time": "2017-02-27T10:12:30+00:00" 520 | }, 521 | { 522 | "name": "phpunit/phpunit", 523 | "version": "4.8.36", 524 | "source": { 525 | "type": "git", 526 | "url": "https://github.com/sebastianbergmann/phpunit.git", 527 | "reference": "46023de9a91eec7dfb06cc56cb4e260017298517" 528 | }, 529 | "dist": { 530 | "type": "zip", 531 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517", 532 | "reference": "46023de9a91eec7dfb06cc56cb4e260017298517", 533 | "shasum": "" 534 | }, 535 | "require": { 536 | "ext-dom": "*", 537 | "ext-json": "*", 538 | "ext-pcre": "*", 539 | "ext-reflection": "*", 540 | "ext-spl": "*", 541 | "php": ">=5.3.3", 542 | "phpspec/prophecy": "^1.3.1", 543 | "phpunit/php-code-coverage": "~2.1", 544 | "phpunit/php-file-iterator": "~1.4", 545 | "phpunit/php-text-template": "~1.2", 546 | "phpunit/php-timer": "^1.0.6", 547 | "phpunit/phpunit-mock-objects": "~2.3", 548 | "sebastian/comparator": "~1.2.2", 549 | "sebastian/diff": "~1.2", 550 | "sebastian/environment": "~1.3", 551 | "sebastian/exporter": "~1.2", 552 | "sebastian/global-state": "~1.0", 553 | "sebastian/version": "~1.0", 554 | "symfony/yaml": "~2.1|~3.0" 555 | }, 556 | "suggest": { 557 | "phpunit/php-invoker": "~1.1" 558 | }, 559 | "bin": [ 560 | "phpunit" 561 | ], 562 | "type": "library", 563 | "extra": { 564 | "branch-alias": { 565 | "dev-master": "4.8.x-dev" 566 | } 567 | }, 568 | "autoload": { 569 | "classmap": [ 570 | "src/" 571 | ] 572 | }, 573 | "notification-url": "https://packagist.org/downloads/", 574 | "license": [ 575 | "BSD-3-Clause" 576 | ], 577 | "authors": [ 578 | { 579 | "name": "Sebastian Bergmann", 580 | "email": "sebastian@phpunit.de", 581 | "role": "lead" 582 | } 583 | ], 584 | "description": "The PHP Unit Testing framework.", 585 | "homepage": "https://phpunit.de/", 586 | "keywords": [ 587 | "phpunit", 588 | "testing", 589 | "xunit" 590 | ], 591 | "time": "2017-06-21T08:07:12+00:00" 592 | }, 593 | { 594 | "name": "phpunit/phpunit-mock-objects", 595 | "version": "2.3.8", 596 | "source": { 597 | "type": "git", 598 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 599 | "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" 600 | }, 601 | "dist": { 602 | "type": "zip", 603 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", 604 | "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", 605 | "shasum": "" 606 | }, 607 | "require": { 608 | "doctrine/instantiator": "^1.0.2", 609 | "php": ">=5.3.3", 610 | "phpunit/php-text-template": "~1.2", 611 | "sebastian/exporter": "~1.2" 612 | }, 613 | "require-dev": { 614 | "phpunit/phpunit": "~4.4" 615 | }, 616 | "suggest": { 617 | "ext-soap": "*" 618 | }, 619 | "type": "library", 620 | "extra": { 621 | "branch-alias": { 622 | "dev-master": "2.3.x-dev" 623 | } 624 | }, 625 | "autoload": { 626 | "classmap": [ 627 | "src/" 628 | ] 629 | }, 630 | "notification-url": "https://packagist.org/downloads/", 631 | "license": [ 632 | "BSD-3-Clause" 633 | ], 634 | "authors": [ 635 | { 636 | "name": "Sebastian Bergmann", 637 | "email": "sb@sebastian-bergmann.de", 638 | "role": "lead" 639 | } 640 | ], 641 | "description": "Mock Object library for PHPUnit", 642 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 643 | "keywords": [ 644 | "mock", 645 | "xunit" 646 | ], 647 | "time": "2015-10-02T06:51:40+00:00" 648 | }, 649 | { 650 | "name": "sebastian/comparator", 651 | "version": "1.2.4", 652 | "source": { 653 | "type": "git", 654 | "url": "https://github.com/sebastianbergmann/comparator.git", 655 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" 656 | }, 657 | "dist": { 658 | "type": "zip", 659 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", 660 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", 661 | "shasum": "" 662 | }, 663 | "require": { 664 | "php": ">=5.3.3", 665 | "sebastian/diff": "~1.2", 666 | "sebastian/exporter": "~1.2 || ~2.0" 667 | }, 668 | "require-dev": { 669 | "phpunit/phpunit": "~4.4" 670 | }, 671 | "type": "library", 672 | "extra": { 673 | "branch-alias": { 674 | "dev-master": "1.2.x-dev" 675 | } 676 | }, 677 | "autoload": { 678 | "classmap": [ 679 | "src/" 680 | ] 681 | }, 682 | "notification-url": "https://packagist.org/downloads/", 683 | "license": [ 684 | "BSD-3-Clause" 685 | ], 686 | "authors": [ 687 | { 688 | "name": "Jeff Welch", 689 | "email": "whatthejeff@gmail.com" 690 | }, 691 | { 692 | "name": "Volker Dusch", 693 | "email": "github@wallbash.com" 694 | }, 695 | { 696 | "name": "Bernhard Schussek", 697 | "email": "bschussek@2bepublished.at" 698 | }, 699 | { 700 | "name": "Sebastian Bergmann", 701 | "email": "sebastian@phpunit.de" 702 | } 703 | ], 704 | "description": "Provides the functionality to compare PHP values for equality", 705 | "homepage": "http://www.github.com/sebastianbergmann/comparator", 706 | "keywords": [ 707 | "comparator", 708 | "compare", 709 | "equality" 710 | ], 711 | "time": "2017-01-29T09:50:25+00:00" 712 | }, 713 | { 714 | "name": "sebastian/diff", 715 | "version": "1.4.3", 716 | "source": { 717 | "type": "git", 718 | "url": "https://github.com/sebastianbergmann/diff.git", 719 | "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" 720 | }, 721 | "dist": { 722 | "type": "zip", 723 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", 724 | "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", 725 | "shasum": "" 726 | }, 727 | "require": { 728 | "php": "^5.3.3 || ^7.0" 729 | }, 730 | "require-dev": { 731 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" 732 | }, 733 | "type": "library", 734 | "extra": { 735 | "branch-alias": { 736 | "dev-master": "1.4-dev" 737 | } 738 | }, 739 | "autoload": { 740 | "classmap": [ 741 | "src/" 742 | ] 743 | }, 744 | "notification-url": "https://packagist.org/downloads/", 745 | "license": [ 746 | "BSD-3-Clause" 747 | ], 748 | "authors": [ 749 | { 750 | "name": "Kore Nordmann", 751 | "email": "mail@kore-nordmann.de" 752 | }, 753 | { 754 | "name": "Sebastian Bergmann", 755 | "email": "sebastian@phpunit.de" 756 | } 757 | ], 758 | "description": "Diff implementation", 759 | "homepage": "https://github.com/sebastianbergmann/diff", 760 | "keywords": [ 761 | "diff" 762 | ], 763 | "time": "2017-05-22T07:24:03+00:00" 764 | }, 765 | { 766 | "name": "sebastian/environment", 767 | "version": "1.3.8", 768 | "source": { 769 | "type": "git", 770 | "url": "https://github.com/sebastianbergmann/environment.git", 771 | "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" 772 | }, 773 | "dist": { 774 | "type": "zip", 775 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", 776 | "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", 777 | "shasum": "" 778 | }, 779 | "require": { 780 | "php": "^5.3.3 || ^7.0" 781 | }, 782 | "require-dev": { 783 | "phpunit/phpunit": "^4.8 || ^5.0" 784 | }, 785 | "type": "library", 786 | "extra": { 787 | "branch-alias": { 788 | "dev-master": "1.3.x-dev" 789 | } 790 | }, 791 | "autoload": { 792 | "classmap": [ 793 | "src/" 794 | ] 795 | }, 796 | "notification-url": "https://packagist.org/downloads/", 797 | "license": [ 798 | "BSD-3-Clause" 799 | ], 800 | "authors": [ 801 | { 802 | "name": "Sebastian Bergmann", 803 | "email": "sebastian@phpunit.de" 804 | } 805 | ], 806 | "description": "Provides functionality to handle HHVM/PHP environments", 807 | "homepage": "http://www.github.com/sebastianbergmann/environment", 808 | "keywords": [ 809 | "Xdebug", 810 | "environment", 811 | "hhvm" 812 | ], 813 | "time": "2016-08-18T05:49:44+00:00" 814 | }, 815 | { 816 | "name": "sebastian/exporter", 817 | "version": "1.2.2", 818 | "source": { 819 | "type": "git", 820 | "url": "https://github.com/sebastianbergmann/exporter.git", 821 | "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" 822 | }, 823 | "dist": { 824 | "type": "zip", 825 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", 826 | "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", 827 | "shasum": "" 828 | }, 829 | "require": { 830 | "php": ">=5.3.3", 831 | "sebastian/recursion-context": "~1.0" 832 | }, 833 | "require-dev": { 834 | "ext-mbstring": "*", 835 | "phpunit/phpunit": "~4.4" 836 | }, 837 | "type": "library", 838 | "extra": { 839 | "branch-alias": { 840 | "dev-master": "1.3.x-dev" 841 | } 842 | }, 843 | "autoload": { 844 | "classmap": [ 845 | "src/" 846 | ] 847 | }, 848 | "notification-url": "https://packagist.org/downloads/", 849 | "license": [ 850 | "BSD-3-Clause" 851 | ], 852 | "authors": [ 853 | { 854 | "name": "Jeff Welch", 855 | "email": "whatthejeff@gmail.com" 856 | }, 857 | { 858 | "name": "Volker Dusch", 859 | "email": "github@wallbash.com" 860 | }, 861 | { 862 | "name": "Bernhard Schussek", 863 | "email": "bschussek@2bepublished.at" 864 | }, 865 | { 866 | "name": "Sebastian Bergmann", 867 | "email": "sebastian@phpunit.de" 868 | }, 869 | { 870 | "name": "Adam Harvey", 871 | "email": "aharvey@php.net" 872 | } 873 | ], 874 | "description": "Provides the functionality to export PHP variables for visualization", 875 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 876 | "keywords": [ 877 | "export", 878 | "exporter" 879 | ], 880 | "time": "2016-06-17T09:04:28+00:00" 881 | }, 882 | { 883 | "name": "sebastian/global-state", 884 | "version": "1.1.1", 885 | "source": { 886 | "type": "git", 887 | "url": "https://github.com/sebastianbergmann/global-state.git", 888 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" 889 | }, 890 | "dist": { 891 | "type": "zip", 892 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", 893 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", 894 | "shasum": "" 895 | }, 896 | "require": { 897 | "php": ">=5.3.3" 898 | }, 899 | "require-dev": { 900 | "phpunit/phpunit": "~4.2" 901 | }, 902 | "suggest": { 903 | "ext-uopz": "*" 904 | }, 905 | "type": "library", 906 | "extra": { 907 | "branch-alias": { 908 | "dev-master": "1.0-dev" 909 | } 910 | }, 911 | "autoload": { 912 | "classmap": [ 913 | "src/" 914 | ] 915 | }, 916 | "notification-url": "https://packagist.org/downloads/", 917 | "license": [ 918 | "BSD-3-Clause" 919 | ], 920 | "authors": [ 921 | { 922 | "name": "Sebastian Bergmann", 923 | "email": "sebastian@phpunit.de" 924 | } 925 | ], 926 | "description": "Snapshotting of global state", 927 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 928 | "keywords": [ 929 | "global state" 930 | ], 931 | "time": "2015-10-12T03:26:01+00:00" 932 | }, 933 | { 934 | "name": "sebastian/recursion-context", 935 | "version": "1.0.5", 936 | "source": { 937 | "type": "git", 938 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 939 | "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" 940 | }, 941 | "dist": { 942 | "type": "zip", 943 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", 944 | "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", 945 | "shasum": "" 946 | }, 947 | "require": { 948 | "php": ">=5.3.3" 949 | }, 950 | "require-dev": { 951 | "phpunit/phpunit": "~4.4" 952 | }, 953 | "type": "library", 954 | "extra": { 955 | "branch-alias": { 956 | "dev-master": "1.0.x-dev" 957 | } 958 | }, 959 | "autoload": { 960 | "classmap": [ 961 | "src/" 962 | ] 963 | }, 964 | "notification-url": "https://packagist.org/downloads/", 965 | "license": [ 966 | "BSD-3-Clause" 967 | ], 968 | "authors": [ 969 | { 970 | "name": "Jeff Welch", 971 | "email": "whatthejeff@gmail.com" 972 | }, 973 | { 974 | "name": "Sebastian Bergmann", 975 | "email": "sebastian@phpunit.de" 976 | }, 977 | { 978 | "name": "Adam Harvey", 979 | "email": "aharvey@php.net" 980 | } 981 | ], 982 | "description": "Provides functionality to recursively process PHP variables", 983 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 984 | "time": "2016-10-03T07:41:43+00:00" 985 | }, 986 | { 987 | "name": "sebastian/version", 988 | "version": "1.0.6", 989 | "source": { 990 | "type": "git", 991 | "url": "https://github.com/sebastianbergmann/version.git", 992 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" 993 | }, 994 | "dist": { 995 | "type": "zip", 996 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 997 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 998 | "shasum": "" 999 | }, 1000 | "type": "library", 1001 | "autoload": { 1002 | "classmap": [ 1003 | "src/" 1004 | ] 1005 | }, 1006 | "notification-url": "https://packagist.org/downloads/", 1007 | "license": [ 1008 | "BSD-3-Clause" 1009 | ], 1010 | "authors": [ 1011 | { 1012 | "name": "Sebastian Bergmann", 1013 | "email": "sebastian@phpunit.de", 1014 | "role": "lead" 1015 | } 1016 | ], 1017 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1018 | "homepage": "https://github.com/sebastianbergmann/version", 1019 | "time": "2015-06-21T13:59:46+00:00" 1020 | }, 1021 | { 1022 | "name": "symfony/yaml", 1023 | "version": "v3.3.10", 1024 | "source": { 1025 | "type": "git", 1026 | "url": "https://github.com/symfony/yaml.git", 1027 | "reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46" 1028 | }, 1029 | "dist": { 1030 | "type": "zip", 1031 | "url": "https://api.github.com/repos/symfony/yaml/zipball/8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46", 1032 | "reference": "8c7bf1e7d5d6b05a690b715729cb4cd0c0a99c46", 1033 | "shasum": "" 1034 | }, 1035 | "require": { 1036 | "php": "^5.5.9|>=7.0.8" 1037 | }, 1038 | "require-dev": { 1039 | "symfony/console": "~2.8|~3.0" 1040 | }, 1041 | "suggest": { 1042 | "symfony/console": "For validating YAML files using the lint command" 1043 | }, 1044 | "type": "library", 1045 | "extra": { 1046 | "branch-alias": { 1047 | "dev-master": "3.3-dev" 1048 | } 1049 | }, 1050 | "autoload": { 1051 | "psr-4": { 1052 | "Symfony\\Component\\Yaml\\": "" 1053 | }, 1054 | "exclude-from-classmap": [ 1055 | "/Tests/" 1056 | ] 1057 | }, 1058 | "notification-url": "https://packagist.org/downloads/", 1059 | "license": [ 1060 | "MIT" 1061 | ], 1062 | "authors": [ 1063 | { 1064 | "name": "Fabien Potencier", 1065 | "email": "fabien@symfony.com" 1066 | }, 1067 | { 1068 | "name": "Symfony Community", 1069 | "homepage": "https://symfony.com/contributors" 1070 | } 1071 | ], 1072 | "description": "Symfony Yaml Component", 1073 | "homepage": "https://symfony.com", 1074 | "time": "2017-10-05T14:43:42+00:00" 1075 | }, 1076 | { 1077 | "name": "webmozart/assert", 1078 | "version": "1.2.0", 1079 | "source": { 1080 | "type": "git", 1081 | "url": "https://github.com/webmozart/assert.git", 1082 | "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" 1083 | }, 1084 | "dist": { 1085 | "type": "zip", 1086 | "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", 1087 | "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", 1088 | "shasum": "" 1089 | }, 1090 | "require": { 1091 | "php": "^5.3.3 || ^7.0" 1092 | }, 1093 | "require-dev": { 1094 | "phpunit/phpunit": "^4.6", 1095 | "sebastian/version": "^1.0.1" 1096 | }, 1097 | "type": "library", 1098 | "extra": { 1099 | "branch-alias": { 1100 | "dev-master": "1.3-dev" 1101 | } 1102 | }, 1103 | "autoload": { 1104 | "psr-4": { 1105 | "Webmozart\\Assert\\": "src/" 1106 | } 1107 | }, 1108 | "notification-url": "https://packagist.org/downloads/", 1109 | "license": [ 1110 | "MIT" 1111 | ], 1112 | "authors": [ 1113 | { 1114 | "name": "Bernhard Schussek", 1115 | "email": "bschussek@gmail.com" 1116 | } 1117 | ], 1118 | "description": "Assertions to validate method input/output with nice error messages.", 1119 | "keywords": [ 1120 | "assert", 1121 | "check", 1122 | "validate" 1123 | ], 1124 | "time": "2016-11-23T20:04:58+00:00" 1125 | } 1126 | ], 1127 | "aliases": [], 1128 | "minimum-stability": "stable", 1129 | "stability-flags": [], 1130 | "prefer-stable": false, 1131 | "prefer-lowest": false, 1132 | "platform": { 1133 | "php": ">=7.0.0" 1134 | }, 1135 | "platform-dev": [] 1136 | } 1137 | --------------------------------------------------------------------------------