From c2cae8de1809c0d6964e6d3a199d46fedce9be09 Mon Sep 17 00:00:00 2001 From: Ryo Utsunomiya Date: Wed, 11 Jun 2014 19:34:14 +0900 Subject: [PATCH 1/7] Add 'use FFMpeg\Coordinate\TimeCode;' Because the namespace FFMpeg\Coordinate\TimeCode is not imported, IDE's inspector raise warning when VideoFilters::clip() is used. --- src/FFMpeg/Filters/Video/VideoFilters.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/FFMpeg/Filters/Video/VideoFilters.php b/src/FFMpeg/Filters/Video/VideoFilters.php index 30b4b2a..88f6207 100644 --- a/src/FFMpeg/Filters/Video/VideoFilters.php +++ b/src/FFMpeg/Filters/Video/VideoFilters.php @@ -12,6 +12,7 @@ namespace FFMpeg\Filters\Video; use FFMpeg\Media\Video; +use FFMpeg\Coordinate\TimeCode; use FFMpeg\Coordinate\Dimension; use FFMpeg\Coordinate\FrameRate; use FFMpeg\Filters\Audio\AudioResamplableFilter; From 7fe6dcf542417aab359d3699958fb75599413f1b Mon Sep 17 00:00:00 2001 From: Ryo Utsunomiya Date: Fri, 18 Jul 2014 14:19:50 +0900 Subject: [PATCH 2/7] Fix Doc comment (width => integer) --- src/FFMpeg/Coordinate/Dimension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FFMpeg/Coordinate/Dimension.php b/src/FFMpeg/Coordinate/Dimension.php index 018d4ed..33ebaea 100644 --- a/src/FFMpeg/Coordinate/Dimension.php +++ b/src/FFMpeg/Coordinate/Dimension.php @@ -40,7 +40,7 @@ class Dimension /** * Returns width. * - * @return width + * @return integer */ public function getWidth() { From 475d1a508a5360c96324c59858fb151c73514a33 Mon Sep 17 00:00:00 2001 From: sylvainv Date: Mon, 28 Jul 2014 17:27:12 +0800 Subject: [PATCH 3/7] Added watermark functionality and unit tests --- src/FFMpeg/Filters/Video/VideoFilters.php | 13 +++ src/FFMpeg/Filters/Video/WatermarkFilter.php | 81 ++++++++++++++++++ .../Filters/Video/WatermarkFilterTest.php | 63 ++++++++++++++ tests/files/waternark.png | Bin 0 -> 3244 bytes 4 files changed, 157 insertions(+) create mode 100644 src/FFMpeg/Filters/Video/WatermarkFilter.php create mode 100644 tests/FFMpeg/Tests/Filters/Video/WatermarkFilterTest.php create mode 100644 tests/files/waternark.png diff --git a/src/FFMpeg/Filters/Video/VideoFilters.php b/src/FFMpeg/Filters/Video/VideoFilters.php index 88f6207..91c929d 100644 --- a/src/FFMpeg/Filters/Video/VideoFilters.php +++ b/src/FFMpeg/Filters/Video/VideoFilters.php @@ -103,4 +103,17 @@ class VideoFilters extends AudioFilters return $this; } + + /** + * @param string $imagePath + * @param array $coordinates + * + * @return $this + */ + public function watermark($imagePath, array $coordinates = array()) + { + $this->media->addFilter(new WatermarkFilter($imagePath, $coordinates)); + + return $this; + } } diff --git a/src/FFMpeg/Filters/Video/WatermarkFilter.php b/src/FFMpeg/Filters/Video/WatermarkFilter.php new file mode 100644 index 0000000..065f177 --- /dev/null +++ b/src/FFMpeg/Filters/Video/WatermarkFilter.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FFMpeg\Filters\Video; + +use FFMpeg\Format\VideoInterface; +use FFMpeg\Media\Video; +use FFMpeg\Coordinate\TimeCode; + +class WatermarkFilter implements VideoFilterInterface +{ + /** @var string */ + private $watermarkPath; + /** @var array */ + private $coordinates; + /** @var integer */ + private $priority; + + + public function __construct($watermarkPath, array $coordinates = array(), $priority = 0) + { + $this->watermarkPath = $watermarkPath; + $this->coordinates = $coordinates; + $this->priority = $priority; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return $this->priority; + } + + /** + * {@inheritdoc} + */ + public function apply(Video $video, VideoInterface $format) + { + $position = isset($this->coordinates['position']) ? $this->coordinates['position'] : 'absolute'; + + switch($position) { + case 'relative': + if (isset($this->coordinates['top'])) { + $y = $this->coordinates['top']; + } + elseif (isset($this->coordinates['bottom'])) { + $y = sprintf('main_h - %d - overlay_h', $this->coordinates['bottom']); + } + else { + $y = 0; + } + + if (isset($this->coordinates['left'])) { + $x = $this->coordinates['left']; + } + elseif (isset($this->coordinates['right'])) { + $x = sprintf('main_w - %d - overlay_w', $this->coordinates['right']); + } + else { + $x = 0; + } + + break; + default: + $x = isset($this->coordinates['x']) ? $this->coordinates['x'] : 0; + $y = isset($this->coordinates['y']) ? $this->coordinates['y'] : 0; + break; + } + + return array('-vf', sprintf('overlay %s:%s', $x, $y)); + } +} diff --git a/tests/FFMpeg/Tests/Filters/Video/WatermarkFilterTest.php b/tests/FFMpeg/Tests/Filters/Video/WatermarkFilterTest.php new file mode 100644 index 0000000..82fb3e1 --- /dev/null +++ b/tests/FFMpeg/Tests/Filters/Video/WatermarkFilterTest.php @@ -0,0 +1,63 @@ + 320, 'height' => 240, 'codec_type' => 'video')); + $streams = new StreamCollection(array($stream)); + + $video = $this->getVideoMock(); + + $format = $this->getMock('FFMpeg\Format\VideoInterface'); + + $filter = new WatermarkFilter(__DIR__ . '/../../files/watermark.png'); + $this->assertEquals(array('-vf', 'overlay 0:0'), $filter->apply($video, $format)); + + // check size of video is unchanged + $this->assertEquals(320, $stream->get('width')); + $this->assertEquals(240, $stream->get('height')); + } + + public function testDifferentCoordinaates() + { + $video = $this->getVideoMock(); + $format = $this->getMock('FFMpeg\Format\VideoInterface'); + + // test position absolute + $filter = new WatermarkFilter(__DIR__ . '/../../files/watermark.png', array( + 'position' => 'absolute', + 'x' => 10, 'y' => 5 + )); + $this->assertEquals(array('-vf', 'overlay 10:5'), $filter->apply($video, $format)); + + // test position relative + $filter = new WatermarkFilter(__DIR__ . '/../../files/watermark.png', array( + 'position' => 'relative', + 'bottom' => 10, 'left' => 5 + )); + $this->assertEquals(array('-vf', 'overlay 5:main_h - 10 - overlay_h'), $filter->apply($video, $format)); + + // test position relative + $filter = new WatermarkFilter(__DIR__ . '/../../files/watermark.png', array( + 'position' => 'relative', + 'bottom' => 5, 'right' => 4 + )); + $this->assertEquals(array('-vf', 'overlay main_w - 4 - overlay_w:main_h - 5 - overlay_h'), $filter->apply($video, $format)); + + // test position relative + $filter = new WatermarkFilter(__DIR__ . '/../../files/watermark.png', array( + 'position' => 'relative', + 'left' => 5, 'top' => 11 + )); + $this->assertEquals(array('-vf', 'overlay 5:11'), $filter->apply($video, $format)); + } +} diff --git a/tests/files/waternark.png b/tests/files/waternark.png new file mode 100644 index 0000000000000000000000000000000000000000..0c96e5480efee31b2caa19d3da9d371351c56252 GIT binary patch literal 3244 zcmc&xYg7{l8XcMuMo5Q=g7Qk8;1jh1$_gftFa!~NP<*YZED#=|(k^1Ap@A*pq$Z%$ z(29acWKfF_TmmWz76ri-6&YqN8nl`c6*4M_6pPk!mYx~+)b*dI$8+|anRDiw@4NSY z_kOvN5eq-Gce4io;KT5+kmUei^PGJlTL1uBZ~e3b001xHz>00tgNS(KY~P$3B15|NZs4elr3J-;BIF z`f-%UtWIr zr-c4k|J`?cH%JiueiL~&)wgm-@VDzfLGeBz?>phQyuRC_|DU6{-1<@<7Ztm-^ppA8 z=^tf)cJqq@V>9`y>Wc$qnS6pO3fvzk%i#Mx-!M{EpDO*}AKihKrRD95^)@#$>qcJW zk#mlYl(#3aWj$9`#U5#Q(A(U|s2)k@6V%hI&((dtLE_Z%_Ol=H2cdY@zkI$Ju`tyA;q4YqDZN1+_#;4TCD}!YzBF3+~5$>>b zc@YuH%8sBlm0C~c)60~kKw%VO$tzUmGJa+sB;J2&5PlMy*H<%<`IMq0ClZW8Y${); zj&s#57T`^3_iU*Luci_aVYa?qQrlt#Z<@Vk^k7kBC_S*yzJ8b>SCkqB7C+f1BIa-x z=~&VOco7k9mG>JJm0x%=hZC&Az(kgEvZVi+h!xy;Tdhm?t{u7~z;B!!giWmL!&U@2 zqzrSyj<{Do@|kW55a8$H!!*lW*9Oy%b{$(BnqcpWwM_7THpYR7h`x}O=#;fSn0{2u z3(jl8B0bhizvfV zHI<;AMfZy&CSL*G>DqJroo}RL9&eA+mBP;*n7>McIFFZ}0ku`+TWvJb^ zojWx9AhA==HRPXl;#^WbS!LeX-A2_42>52F`p6!uWUv_$uS~KU#(D2z7dd*JlGLz^ zlnHcuj{eKC7!Iq1 z#9$XXHf3I1E-6PU-Kq;_K+YjcKXv$sy&Q+Tei8h=72v7XAD%p_~xl&syBQ)hJWt9BbUt z-0lO3C-neM{vHJ(v}oSyg3Wsbm@dLa;9AI1i{q_dMzhA4Ji`m$*oW--Xje)9gMk$e z1Z>z6u*n0KdouJqQ{gM?IZp!S`S{$BX@Vg!zX{yXu;f3UOqV_|=2j3ba*u5tYH9a^ z&Mj=r_9%I*=jqu`GWxT}x*54B!>3+`WewfI=!;aQU2lzsY+k1_D;x4}r`Tb-v%U4! z?x|xD{AIR$)vo~^5CPlO4Hf88bSSgAK=G}V&)Y^ehnLGA%(>@4!10#v96({H<;mUJ z;h7&0@Tqy5oeMQPQD*bF)d%mV)gYj7obTE(y7MXokGf7}da$|*XGyo_Lq7F z(W1$^26I{z0t%Na=eK?yh~P}Njk89AGIZ`YQzMq%CrHgv^*$R=81Fiek}UJjCgtg& zQkR|}6cXbb4_BkX&{7^HC>zz2DUCjkfXB36jumsIdL|QhTZGU@etYBfh%zcgpY)E$I-2>MO*$BI` zmmHeO4NaWgSN|j1i?~uX_;;4OR<3=m+sOaMaX*KnkO6TKhg%}BTgS@r972j$a&kDb zj9sMBUT~r346A>_$$}QM!({fy#vL5q{!aY$?q4X@Rb$$$qVigxE&&HsvZDVV{7apz z!kB=+ST?btgxz0xnj^^dc(s;0PyY2XO)5A2mARpqyGmH^CTUNpf%T5oRH#kf>0JML z)(a?(|6-Q6_xw@7v#kt}P4WN&1|Tu}pnue_f4#2XjG)Z_jJzL6;!p#?1DbwIs+VVl RR|5b5gfEB)sSJ)Ieh1?P- Date: Wed, 30 Jul 2014 17:59:55 +0800 Subject: [PATCH 4/7] Added custom filter support --- src/FFMpeg/Filters/Video/CustomFilter.php | 51 +++++++++++++++++++ .../Tests/Filters/Video/CustomFilterTest.php | 20 ++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/FFMpeg/Filters/Video/CustomFilter.php create mode 100644 tests/FFMpeg/Tests/Filters/Video/CustomFilterTest.php diff --git a/src/FFMpeg/Filters/Video/CustomFilter.php b/src/FFMpeg/Filters/Video/CustomFilter.php new file mode 100644 index 0000000..4946c62 --- /dev/null +++ b/src/FFMpeg/Filters/Video/CustomFilter.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace FFMpeg\Filters\Video; + +use FFMpeg\Format\VideoInterface; +use FFMpeg\Media\Video; + +class CustomFilter implements VideoFilterInterface +{ + /** @var string */ + private $filter; + /** @var integer */ + private $priority; + + /** + * A custom filter, useful if you want to build complex filters + * + * @param string $filter + * @param int $priority + */ + public function __construct($filter, $priority = 0) + { + $this->filter = $filter; + $this->priority = $priority; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return $this->priority; + } + + /** + * {@inheritdoc} + */ + public function apply(Video $video, VideoInterface $format) + { + $commands = array('-vf', $this->filter); + return $commands; + } +} diff --git a/tests/FFMpeg/Tests/Filters/Video/CustomFilterTest.php b/tests/FFMpeg/Tests/Filters/Video/CustomFilterTest.php new file mode 100644 index 0000000..ff8ad75 --- /dev/null +++ b/tests/FFMpeg/Tests/Filters/Video/CustomFilterTest.php @@ -0,0 +1,20 @@ +getVideoMock(); + $format = $this->getMock('FFMpeg\Format\VideoInterface'); + + $filter = new CustomFilter('whatever i put would end up as a filter'); + $this->assertEquals(array('-vf', 'whatever i put would end up as a filter'), $filter->apply($video, $format)); + } +} From 634e67d7ec0e88fdae3eaddf56043e6756085399 Mon Sep 17 00:00:00 2001 From: sylvainv Date: Fri, 1 Aug 2014 00:03:13 +0800 Subject: [PATCH 5/7] Allow to set bframe support in a format --- src/FFMpeg/Format/Video/X264.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/FFMpeg/Format/Video/X264.php b/src/FFMpeg/Format/Video/X264.php index 40010fd..55b7a6e 100644 --- a/src/FFMpeg/Format/Video/X264.php +++ b/src/FFMpeg/Format/Video/X264.php @@ -16,11 +16,15 @@ namespace FFMpeg\Format\Video; */ class X264 extends DefaultVideo { + /** @var boolean */ + private $bframesSupport; + public function __construct($audioCodec = 'libfaac', $videoCodec = 'libx264') { $this ->setAudioCodec($audioCodec) ->setVideoCodec($videoCodec); + $this->bframesSupport = true; } /** @@ -28,7 +32,18 @@ class X264 extends DefaultVideo */ public function supportBFrames() { - return true; + return $this->bframesSupport; + } + + /** + * @param $support + * @return X264 + */ + public function setBFramesSupport($support) + { + $this->bframesSupport = $support; + + return $this; } /** From 3a080613e66b0daaea1b896b27397d9e0b496702 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Tue, 12 Aug 2014 20:49:42 +0200 Subject: [PATCH 6/7] Fix CS --- src/FFMpeg/FFMpegServiceProvider.php | 2 -- src/FFMpeg/Filters/Audio/AudioFilters.php | 1 - src/FFMpeg/Filters/Video/CustomFilter.php | 3 ++- src/FFMpeg/Filters/Video/VideoFilters.php | 2 +- src/FFMpeg/Filters/Video/WatermarkFilter.php | 16 +++++----------- src/FFMpeg/Media/AbstractMediaType.php | 1 - src/FFMpeg/Media/Frame.php | 1 - src/FFMpeg/Media/Video.php | 9 ++++----- 8 files changed, 12 insertions(+), 23 deletions(-) diff --git a/src/FFMpeg/FFMpegServiceProvider.php b/src/FFMpeg/FFMpegServiceProvider.php index 4bb489b..6aa9735 100644 --- a/src/FFMpeg/FFMpegServiceProvider.php +++ b/src/FFMpeg/FFMpegServiceProvider.php @@ -12,8 +12,6 @@ namespace FFMpeg; use Doctrine\Common\Cache\ArrayCache; -use FFMpeg\FFMpeg; -use FFMpeg\FFProbe; use Silex\Application; use Silex\ServiceProviderInterface; diff --git a/src/FFMpeg/Filters/Audio/AudioFilters.php b/src/FFMpeg/Filters/Audio/AudioFilters.php index ddce8f1..fe328c5 100644 --- a/src/FFMpeg/Filters/Audio/AudioFilters.php +++ b/src/FFMpeg/Filters/Audio/AudioFilters.php @@ -3,7 +3,6 @@ namespace FFMpeg\Filters\Audio; use FFMpeg\Media\Audio; -use FFMpeg\Filters\Audio\AudioResamplableFilter; class AudioFilters { diff --git a/src/FFMpeg/Filters/Video/CustomFilter.php b/src/FFMpeg/Filters/Video/CustomFilter.php index 4946c62..eb75079 100644 --- a/src/FFMpeg/Filters/Video/CustomFilter.php +++ b/src/FFMpeg/Filters/Video/CustomFilter.php @@ -24,7 +24,7 @@ class CustomFilter implements VideoFilterInterface * A custom filter, useful if you want to build complex filters * * @param string $filter - * @param int $priority + * @param int $priority */ public function __construct($filter, $priority = 0) { @@ -46,6 +46,7 @@ class CustomFilter implements VideoFilterInterface public function apply(Video $video, VideoInterface $format) { $commands = array('-vf', $this->filter); + return $commands; } } diff --git a/src/FFMpeg/Filters/Video/VideoFilters.php b/src/FFMpeg/Filters/Video/VideoFilters.php index 91c929d..55281e4 100644 --- a/src/FFMpeg/Filters/Video/VideoFilters.php +++ b/src/FFMpeg/Filters/Video/VideoFilters.php @@ -106,7 +106,7 @@ class VideoFilters extends AudioFilters /** * @param string $imagePath - * @param array $coordinates + * @param array $coordinates * * @return $this */ diff --git a/src/FFMpeg/Filters/Video/WatermarkFilter.php b/src/FFMpeg/Filters/Video/WatermarkFilter.php index 065f177..40bb8fc 100644 --- a/src/FFMpeg/Filters/Video/WatermarkFilter.php +++ b/src/FFMpeg/Filters/Video/WatermarkFilter.php @@ -13,7 +13,6 @@ namespace FFMpeg\Filters\Video; use FFMpeg\Format\VideoInterface; use FFMpeg\Media\Video; -use FFMpeg\Coordinate\TimeCode; class WatermarkFilter implements VideoFilterInterface { @@ -24,7 +23,6 @@ class WatermarkFilter implements VideoFilterInterface /** @var integer */ private $priority; - public function __construct($watermarkPath, array $coordinates = array(), $priority = 0) { $this->watermarkPath = $watermarkPath; @@ -47,25 +45,21 @@ class WatermarkFilter implements VideoFilterInterface { $position = isset($this->coordinates['position']) ? $this->coordinates['position'] : 'absolute'; - switch($position) { + switch ($position) { case 'relative': if (isset($this->coordinates['top'])) { $y = $this->coordinates['top']; - } - elseif (isset($this->coordinates['bottom'])) { + } elseif (isset($this->coordinates['bottom'])) { $y = sprintf('main_h - %d - overlay_h', $this->coordinates['bottom']); - } - else { + } else { $y = 0; } if (isset($this->coordinates['left'])) { $x = $this->coordinates['left']; - } - elseif (isset($this->coordinates['right'])) { + } elseif (isset($this->coordinates['right'])) { $x = sprintf('main_w - %d - overlay_w', $this->coordinates['right']); - } - else { + } else { $x = 0; } diff --git a/src/FFMpeg/Media/AbstractMediaType.php b/src/FFMpeg/Media/AbstractMediaType.php index 45bb0c6..5523c0b 100644 --- a/src/FFMpeg/Media/AbstractMediaType.php +++ b/src/FFMpeg/Media/AbstractMediaType.php @@ -14,7 +14,6 @@ namespace FFMpeg\Media; use FFMpeg\Driver\FFMpegDriver; use FFMpeg\FFProbe; use FFMpeg\Filters\FiltersCollection; -use FFMpeg\Media\MediaTypeInterface; abstract class AbstractMediaType implements MediaTypeInterface { diff --git a/src/FFMpeg/Media/Frame.php b/src/FFMpeg/Media/Frame.php index b50e549..7b08eb3 100644 --- a/src/FFMpeg/Media/Frame.php +++ b/src/FFMpeg/Media/Frame.php @@ -18,7 +18,6 @@ use FFMpeg\Driver\FFMpegDriver; use FFMpeg\FFProbe; use FFMpeg\Exception\RuntimeException; use FFMpeg\Coordinate\TimeCode; -use FFMpeg\Media\Video; class Frame extends AbstractMediaType { diff --git a/src/FFMpeg/Media/Video.php b/src/FFMpeg/Media/Video.php index ae2996f..f802f5b 100644 --- a/src/FFMpeg/Media/Video.php +++ b/src/FFMpeg/Media/Video.php @@ -22,7 +22,6 @@ use FFMpeg\Format\FormatInterface; use FFMpeg\Format\ProgressableInterface; use FFMpeg\Format\AudioInterface; use FFMpeg\Format\VideoInterface; -use FFMpeg\Media\Frame; use Neutron\TemporaryFilesystem\Manager as FsManager; class Video extends Audio @@ -69,12 +68,12 @@ class Video extends Audio if ($this->driver->getConfiguration()->has('ffmpeg.threads')) { $filters->add(new SimpleFilter(array('-threads', $this->driver->getConfiguration()->get('ffmpeg.threads')))); } - if ($format instanceOf VideoInterface) { + if ($format instanceof VideoInterface) { if (null !== $format->getVideoCodec()) { $filters->add(new SimpleFilter(array('-vcodec', $format->getVideoCodec()))); } } - if ($format instanceOf AudioInterface) { + if ($format instanceof AudioInterface) { if (null !== $format->getAudioCodec()) { $filters->add(new SimpleFilter(array('-acodec', $format->getAudioCodec()))); } @@ -84,7 +83,7 @@ class Video extends Audio $commands = array_merge($commands, $filter->apply($this, $format)); } - if ($format instanceOf VideoInterface) { + if ($format instanceof VideoInterface) { $commands[] = '-b:v'; $commands[] = $format->getKiloBitrate() . 'k'; $commands[] = '-refs'; @@ -109,7 +108,7 @@ class Video extends Audio $commands[] = '1'; } - if ($format instanceOf AudioInterface) { + if ($format instanceof AudioInterface) { if (null !== $format->getAudioKiloBitrate()) { $commands[] = '-b:a'; $commands[] = $format->getAudioKiloBitrate() . 'k'; From 559df352c5080c9e46abffb4cbbf830bceb60067 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Tue, 12 Aug 2014 20:51:22 +0200 Subject: [PATCH 7/7] Minor fixes --- src/FFMpeg/Format/Video/X264.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/FFMpeg/Format/Video/X264.php b/src/FFMpeg/Format/Video/X264.php index 55b7a6e..80e4b67 100644 --- a/src/FFMpeg/Format/Video/X264.php +++ b/src/FFMpeg/Format/Video/X264.php @@ -17,14 +17,13 @@ namespace FFMpeg\Format\Video; class X264 extends DefaultVideo { /** @var boolean */ - private $bframesSupport; + private $bframesSupport = true; public function __construct($audioCodec = 'libfaac', $videoCodec = 'libx264') { $this ->setAudioCodec($audioCodec) ->setVideoCodec($videoCodec); - $this->bframesSupport = true; } /** @@ -37,6 +36,7 @@ class X264 extends DefaultVideo /** * @param $support + * * @return X264 */ public function setBFramesSupport($support) @@ -70,6 +70,9 @@ class X264 extends DefaultVideo return 2; } + /** + * @return int + */ public function getModulus() { return 2;