diff --git a/.gitignore b/.gitignore index 4ebb78c..af58555 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .DS_Store vendor .idea -composer.lock \ No newline at end of file +.phpunit.cache +coverage.xml +composer.lock diff --git a/docs/README.md b/docs/README.md index 7aa0101..879414f 100755 --- a/docs/README.md +++ b/docs/README.md @@ -32,6 +32,7 @@ Moreover, Larupload can calculate the dominant colors of videos and images, as w * Extract the width and height of the image * Extract width, height, and duration of the video * Extract the duration of the audio +* Convert audio file formats * Extract dominant color from the image and video * Automatically create a cover image for video files * Possibility to upload a cover for every file diff --git a/docs/advanced-usage/attachment/complete-example.md b/docs/advanced-usage/attachment/complete-example.md index 8222aab..698fcda 100644 --- a/docs/advanced-usage/attachment/complete-example.md +++ b/docs/advanced-usage/attachment/complete-example.md @@ -41,6 +41,13 @@ class Media extends Model ->image('thumbnail', 250, 250, LaruploadMediaStyle::AUTO) ->image('crop_mode', 1100, 1100, LaruploadMediaStyle::CROP) ->image('portrait_mode', 1000, 1000, LaruploadMediaStyle::SCALE_WIDTH) + ->audio('audio_wav', new Wav()) + ->audio('audio_flac', new Flac()) + ->audio('audio_aac', new Aac()) + ->audio( + name: 'audio_mp3', + format: (new Mp3())->setAudioKiloBitrate(192)->setAudioChannels(2) + ) ->video('thumbnail', 250, 250, LaruploadMediaStyle::AUTO) ->video('crop_mode', 1100, 1100, LaruploadMediaStyle::CROP) ->video('portrait_mode', 1000, 1000, LaruploadMediaStyle::SCALE_WIDTH) diff --git a/docs/advanced-usage/attachment/media-styles.md b/docs/advanced-usage/attachment/media-styles.md index 4ffaf26..b3b5392 100644 --- a/docs/advanced-usage/attachment/media-styles.md +++ b/docs/advanced-usage/attachment/media-styles.md @@ -45,6 +45,43 @@ class Media extends Model + + +### Audio Style + +
IndexNameTypeRequiredDefaultDescription
1namestringtruestyle name. examples: hq, lq, ...
2formatMp3|Aac|Wav|FlacfalseMp3the format of the converted audio file
+ +```php +audio('hq', new Wav()) + ]; + } +} +``` + + + + + + + ### Video Style
IndexNameTypeRequiredDefaultDescription
1namestringtruestyle name. examples: thumbnail, small, ...
2width?intfalsenullwidth of the manipulated video
3height?intfalsenullheight of the manipulated video
4modeLaruploadMediaStylefalseSCALE_HEIGHTthis argument specifies how Larupload should manipulate the uploaded video and can take on any of the following values: FIT, AUTO, SCALE_WIDTH, SCALE_HEIGHT, CROP
5formatX264falsenew X264by default, the encoding format for video is X264. However, users can specify additional options for this format, including adjusting the kilobitrate for both audio and video. This allows for more precise configuration and optimization of the user's encoding preferences.
6paddingboolfalsefalseIf set to true, padding will be applied to the video using a black color in order to fit the given dimensions.
diff --git a/docs/cover/upload-cover.md b/docs/cover/upload-cover.md index 0944399..6854974 100644 --- a/docs/cover/upload-cover.md +++ b/docs/cover/upload-cover.md @@ -2,20 +2,17 @@ In Larupload, covers are associated with the original files and must be uploaded using the `attach()` function. When uploading a file, you can also include a cover as the second argument. If a cover is provided, it will be assigned to the uploaded file and the [automatic cover creation](#user-content-fn-1)[^1] by the package will be prevented. -
$file = $request->file('file');
+```php
+$file = $request->file('file');
 $cover = $request->file('cover');
+# or
+$upload->attachment('file')->attach($file, $cover);
 
-$upload->attachment('file')->attach($file, $cover);
 $upload->save();
-
+``` [^1]: it's only available for image and videos - -[^2]: ```php - # or - $upload->file->attach($file, $cover); - ``` diff --git a/docs/standalone-uploader/customization.md b/docs/standalone-uploader/customization.md index 81eafff..13e4608 100644 --- a/docs/standalone-uploader/customization.md +++ b/docs/standalone-uploader/customization.md @@ -21,6 +21,7 @@ $upload = Larupload::init('path') ->namingMethod(LaruploadNamingMethod::HASH_FILE) ->image('thumbnail', 1000, 750, LaruploadMediaStyle::CROP) ->video('thumbnail', 1000, 750, LaruploadMediaStyle::CROP) + ->audio('wav', new Wav()) ->stream( name: '480p', width: 640, diff --git a/src/Actions/Cover/GenerateCoverFromFileAction.php b/src/Actions/Cover/GenerateCoverFromFileAction.php index 0190865..fbe4ec6 100644 --- a/src/Actions/Cover/GenerateCoverFromFileAction.php +++ b/src/Actions/Cover/GenerateCoverFromFileAction.php @@ -14,6 +14,10 @@ class GenerateCoverFromFileAction private readonly string $fileName; private readonly mixed $ffmpegCaptureFrame; private array $output; + private array $availableStyles = [ + LaruploadFileType::VIDEO, LaruploadFileType::IMAGE + ]; + public function __construct(private readonly UploadedFile $file, private readonly CoverActionData $data) { @@ -29,6 +33,10 @@ public static function make(UploadedFile $file, CoverActionData $data): static public function run(string $path): array { + if (!in_array($this->data->type, $this->availableStyles)) { + return $this->output; + } + Storage::disk($this->data->disk)->makeDirectory($path); $format = $this->fileFormat(); diff --git a/src/Actions/FixExceptionNamesAction.php b/src/Actions/FixExceptionNamesAction.php index 3bee0fc..ed648fc 100644 --- a/src/Actions/FixExceptionNamesAction.php +++ b/src/Actions/FixExceptionNamesAction.php @@ -3,6 +3,7 @@ namespace Mostafaznv\Larupload\Actions; use Illuminate\Support\Str; +use Mostafaznv\Larupload\DTOs\Style\Style; use Mostafaznv\Larupload\Larupload; /** @@ -15,23 +16,30 @@ class FixExceptionNamesAction { public function __construct( private readonly string $name, - private readonly string $style + private readonly string $styleName, + private readonly ?Style $style = null, ) {} - public static function make(string $name, string $style): self + public static function make(string $name, string $styleName, ?Style $style = null): self { - return new self($name, $style); + return new self($name, $styleName, $style); } public function run(): string { - if (!in_array($this->style, [Larupload::ORIGINAL_FOLDER, Larupload::COVER_FOLDER])) { - if (Str::endsWith($this->name, 'svg')) { - return str_replace('svg', 'jpg', $this->name); + $name = $this->name; + + if ($this->style) { + $name = larupload_style_path($name, $this->style->extension()); + } + + if (!in_array($this->styleName, [Larupload::ORIGINAL_FOLDER, Larupload::COVER_FOLDER])) { + if (Str::endsWith($name, 'svg')) { + return str_replace('svg', 'jpg', $name); } } - return $this->name; + return $name; } } diff --git a/src/Concerns/Storage/Attachment/QueueAttachment.php b/src/Concerns/Storage/Attachment/QueueAttachment.php index 9fa41fb..74e07e5 100755 --- a/src/Concerns/Storage/Attachment/QueueAttachment.php +++ b/src/Concerns/Storage/Attachment/QueueAttachment.php @@ -2,16 +2,17 @@ namespace Mostafaznv\Larupload\Concerns\Storage\Attachment; - use Illuminate\Http\Exceptions\HttpResponseException; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\URL; use Mostafaznv\Larupload\Actions\GuessLaruploadFileTypeAction; +use Mostafaznv\Larupload\Enums\LaruploadFileType; use Mostafaznv\Larupload\Jobs\ProcessFFMpeg; use Mostafaznv\Larupload\Larupload; + trait QueueAttachment { /** @@ -22,7 +23,12 @@ public function handleFFMpegQueue(bool $isLastOne = false, bool $standalone = fa $this->file = $this->prepareFileForFFMpegProcess(); $this->type = GuessLaruploadFileTypeAction::make($this->file)->calc(); - $this->handleVideoStyles($this->id); + if ($this->type == LaruploadFileType::VIDEO) { + $this->handleVideoStyles($this->id); + } + else if ($this->type == LaruploadFileType::AUDIO) { + $this->handleAudioStyles($this->id); + } if ($this->driverIsNotLocal() and $isLastOne) { Storage::disk($this->localDisk)->deleteDirectory( diff --git a/src/Concerns/Storage/Attachment/RetrieveAttachment.php b/src/Concerns/Storage/Attachment/RetrieveAttachment.php index 802fe5d..40ca310 100755 --- a/src/Concerns/Storage/Attachment/RetrieveAttachment.php +++ b/src/Concerns/Storage/Attachment/RetrieveAttachment.php @@ -43,7 +43,8 @@ public function urls(): object $allStyles = array_merge( $staticStyles, array_keys($this->imageStyles), - array_keys($this->videoStyles) + array_keys($this->videoStyles), + array_keys($this->audioStyles) ); foreach ($allStyles as $style) { diff --git a/src/Concerns/Storage/Attachment/StyleAttachment.php b/src/Concerns/Storage/Attachment/StyleAttachment.php index 2188d66..e60b55f 100755 --- a/src/Concerns/Storage/Attachment/StyleAttachment.php +++ b/src/Concerns/Storage/Attachment/StyleAttachment.php @@ -53,6 +53,29 @@ protected function handleStyles(string $id, Model|string $model, bool $standalon $this->handleVideoStyles($id); } + break; + + case LaruploadFileType::AUDIO: + if ($this->ffmpegQueue) { + if ($this->driverIsNotLocal()) { + $this->uploadOriginalFile($id, $this->localDisk); + } + + if ($model instanceof Model) { + $this->initializeFFMpegQueue( + $model->id, $model->getMorphClass(), $standalone + ); + } + else { + $this->initializeFFMpegQueue( + $id, $model, $standalone + ); + } + } + else { + $this->handleAudioStyles($id); + } + break; } } @@ -82,6 +105,22 @@ protected function handleVideoStyles($id): void } } + /** + * Handle styles for audios + * + * @param $id + */ + protected function handleAudioStyles($id): void + { + foreach ($this->audioStyles as $name => $style) { + $path = $this->getBasePath($id, $name); + Storage::disk($this->disk)->makeDirectory($path); + $saveTo = "$path/{$this->output['name']}"; + + $this->ffmpeg()->audio($style, $saveTo); + } + } + /** * Prepare style path * this function will use to prepare full path of given style to generate url/download response @@ -97,7 +136,7 @@ protected function prepareStylePath(string $style): ?string Larupload::STREAM_FOLDER ]; - if (isset($this->id) and (in_array($style, $staticStyles) or array_key_exists($style, $this->imageStyles) or array_key_exists($style, $this->videoStyles))) { + if (isset($this->id) and (in_array($style, $staticStyles) or array_key_exists($style, $this->imageStyles) or array_key_exists($style, $this->videoStyles) or array_key_exists($style, $this->audioStyles))) { $name = $style == Larupload::COVER_FOLDER ? $this->output['cover'] : $this->output['name']; @@ -117,7 +156,8 @@ protected function prepareStylePath(string $style): ?string return null; } else if ($name and $this->styleHasFile($style)) { - $name = FixExceptionNamesAction::make($name, $style)->run(); + + $name = FixExceptionNamesAction::make($name, $style, $this->getStyle($style))->run(); $path = $this->getBasePath($this->id, $style); return "$path/$name"; diff --git a/src/Concerns/Storage/UploadEntity/UploadEntityStyle.php b/src/Concerns/Storage/UploadEntity/UploadEntityStyle.php index 4d577f8..fb58008 100755 --- a/src/Concerns/Storage/UploadEntity/UploadEntityStyle.php +++ b/src/Concerns/Storage/UploadEntity/UploadEntityStyle.php @@ -3,9 +3,15 @@ namespace Mostafaznv\Larupload\Concerns\Storage\UploadEntity; +use FFMpeg\Format\Audio\Aac; +use FFMpeg\Format\Audio\Flac; +use FFMpeg\Format\Audio\Mp3; +use FFMpeg\Format\Audio\Wav; use FFMpeg\Format\Video\X264; +use Mostafaznv\Larupload\DTOs\Style\AudioStyle; use Mostafaznv\Larupload\DTOs\Style\StreamStyle; use Mostafaznv\Larupload\DTOs\Style\ImageStyle; +use Mostafaznv\Larupload\DTOs\Style\Style; use Mostafaznv\Larupload\DTOs\Style\VideoStyle; use Mostafaznv\Larupload\Enums\LaruploadFileType; use Mostafaznv\Larupload\Enums\LaruploadMediaStyle; @@ -28,6 +34,13 @@ trait UploadEntityStyle */ protected array $videoStyles = []; + /** + * Styles for audio files + * + * @var AudioStyle[] + */ + protected array $audioStyles = []; + /** * Stream styles * @@ -53,6 +66,11 @@ public function getVideoStyles(): array return $this->videoStyles; } + public function getAudioStyles(): array + { + return $this->audioStyles; + } + public function image(string $name, ?int $width = null, ?int $height = null, LaruploadMediaStyle $mode = LaruploadMediaStyle::AUTO): UploadEntities { $this->imageStyles[$name] = ImageStyle::make($name, $width, $height, $mode); @@ -67,6 +85,13 @@ public function video(string $name, ?int $width = null, ?int $height = null, Lar return $this; } + public function audio(string $name, Mp3|Aac|Wav|Flac $format = new Mp3): UploadEntities + { + $this->audioStyles[$name] = AudioStyle::make($name, $format); + + return $this; + } + public function stream(string $name, int $width, int $height, X264 $format, LaruploadMediaStyle $mode = LaruploadMediaStyle::SCALE_HEIGHT, bool $padding = false): UploadEntities { $this->streams[$name] = StreamStyle::make($name, $width, $height, $format, $mode, $padding); @@ -81,26 +106,36 @@ public function coverStyle(string $name, ?int $width = null, ?int $height = null return $this; } - protected function styleHasFile(string $style): bool + protected function getStyle(string $style): ?Style { - if (in_array($style, [Larupload::ORIGINAL_FOLDER, Larupload::COVER_FOLDER])) { - return true; - } - $type = $this->output['type']; $types = [ LaruploadFileType::VIDEO->name, + LaruploadFileType::AUDIO->name, LaruploadFileType::IMAGE->name ]; if (in_array($type, $types)) { - $styles = $type === LaruploadFileType::IMAGE->name - ? $this->imageStyles - : $this->videoStyles; + $styles = match ($type) { + LaruploadFileType::VIDEO->name => $this->videoStyles, + LaruploadFileType::AUDIO->name => $this->audioStyles, + LaruploadFileType::IMAGE->name => $this->imageStyles, + }; + + if (isset($styles[$style])) { + return $styles[$style]; + } + } - return array_key_exists($style, $styles); + return null; + } + + protected function styleHasFile(string $style): bool + { + if (in_array($style, [Larupload::ORIGINAL_FOLDER, Larupload::COVER_FOLDER])) { + return true; } - return false; + return $this->getStyle($style) !== null; } } diff --git a/src/DTOs/Style/AudioStyle.php b/src/DTOs/Style/AudioStyle.php new file mode 100755 index 0000000..31a9080 --- /dev/null +++ b/src/DTOs/Style/AudioStyle.php @@ -0,0 +1,43 @@ +format = $format; + + } + + public static function make(string $name, Mp3|Aac|Wav|Flac $format = new Mp3): self + { + return new self($name, $format); + } + + public function extension(): ?string + { + if ($this->format instanceof Aac) { + return 'aac'; + } + else if ($this->format instanceof Wav) { + return 'wav'; + } + else if ($this->format instanceof Flac) { + return 'flac'; + } + + return 'mp3'; + } +} diff --git a/src/DTOs/Style/Style.php b/src/DTOs/Style/Style.php index 42ccada..739a635 100755 --- a/src/DTOs/Style/Style.php +++ b/src/DTOs/Style/Style.php @@ -4,6 +4,7 @@ use Exception; + abstract class Style { public function __construct( @@ -16,6 +17,11 @@ public function __construct( } + public function extension(): ?string + { + return null; + } + private function validate(): void { $this->validateName(); diff --git a/src/Helpers/Utils.php b/src/Helpers/Utils.php index 0942b16..2d6f93c 100755 --- a/src/Helpers/Utils.php +++ b/src/Helpers/Utils.php @@ -72,10 +72,12 @@ function split_larupload_path(string $dir): array * * @param string $disk * @param string $saveTo + * @param string|null $extension * @return array */ - function get_larupload_save_path(string $disk, string $saveTo): array + function get_larupload_save_path(string $disk, string $saveTo, ?string $extension = null): array { + $saveTo = larupload_style_path($saveTo, $extension); $permanent = \Illuminate\Support\Facades\Storage::disk($disk)->path($saveTo); list($path, $name) = split_larupload_path($saveTo); @@ -187,4 +189,24 @@ function file_is_valid(mixed $file, string $name, string $type): bool } } +if (!function_exists('larupload_style_path')) { + /** + * Change path extension + * + * @param string $path + * @param string|null $extension + * @return string + */ + function larupload_style_path(string $path, ?string $extension): string + { + if ($extension) { + $info = pathinfo($path); + + return $info['dirname'] . DIRECTORY_SEPARATOR . $info['filename'] . '.' . $extension; + } + + return $path; + } +} + diff --git a/src/Storage/FFMpeg/FFMpeg.php b/src/Storage/FFMpeg/FFMpeg.php index 12783ef..770d6ce 100755 --- a/src/Storage/FFMpeg/FFMpeg.php +++ b/src/Storage/FFMpeg/FFMpeg.php @@ -14,6 +14,7 @@ use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Log; use Mostafaznv\Larupload\DTOs\FFMpeg\FFMpegMeta; +use Mostafaznv\Larupload\DTOs\Style\AudioStyle; use Mostafaznv\Larupload\DTOs\Style\ImageStyle; use Mostafaznv\Larupload\DTOs\Style\StreamStyle; use Mostafaznv\Larupload\DTOs\Style\VideoStyle; @@ -21,6 +22,7 @@ use Mostafaznv\Larupload\Storage\Image; use Psr\Log\LoggerInterface; + class FFMpeg { private readonly UploadedFile $file; @@ -122,6 +124,15 @@ public function capture(int|float|null $fromSeconds, ImageStyle $style, string $ return $dominantColor; } + public function audio(AudioStyle $style, string $saveTo): void + { + $saveTo = get_larupload_save_path($this->disk, $saveTo, $style->extension()); + + $this->media->save($style->format, $saveTo['local']); + + larupload_finalize_save($this->disk, $saveTo); + } + public function manipulate(VideoStyle $style, string $saveTo): void { $saveTo = get_larupload_save_path($this->disk, $saveTo); diff --git a/tests/Feature/AudioStyleTest.php b/tests/Feature/AudioStyleTest.php new file mode 100644 index 0000000..510b132 --- /dev/null +++ b/tests/Feature/AudioStyleTest.php @@ -0,0 +1,138 @@ +withAllAudios(); + $model = save($model, mp3()); + + $attachment = $model->attachment('main_file'); + $mp3 = urlToAudio($attachment->url('audio_mp3')); + $wav = urlToAudio($attachment->url('audio_wav')); + $flac = urlToAudio($attachment->url('audio_flac')); + + expect($attachment->url('cover')) + ->toBeNull() + // mp3 + ->and($attachment->url('audio_mp3')) + ->toBeTruthy() + ->toBeString() + ->toBeExists() + ->and($mp3->width) + ->toBeNull() + ->and($mp3->height) + ->toBeNull() + ->and($mp3->duration) + ->toBe(67) + // wav + ->and($attachment->url('audio_wav')) + ->toBeTruthy() + ->toBeString() + ->toBeExists() + ->and($wav->width) + ->toBeNull() + ->and($wav->height) + ->toBeNull() + ->and($wav->duration) + ->toBe(67) + // flac + ->and($attachment->url('audio_flac')) + ->toBeTruthy() + ->toBeString() + ->toBeExists() + ->and($flac->width) + ->toBeNull() + ->and($flac->height) + ->toBeNull() + ->and($flac->duration) + ->toBe(67); + +})->with('models'); + +it('will generate audio styles in standalone mode correctly', function() { + $upload = Larupload::init('uploader') + ->audio('audio_mp3', new Mp3()) + ->audio('audio_wav', new Wav()) + ->audio('audio_flac', new Flac()) + ->upload(mp3()); + + $mp3 = urlToVideo($upload->audio_mp3); + $wav = urlToVideo($upload->audio_wav); + $flac = urlToVideo($upload->audio_flac); + + expect($upload->cover) + ->toBeNull() + // mp3 + ->and($upload->audio_mp3) + ->toBeTruthy() + ->toBeString() + ->toBeExists() + ->and($mp3->width) + ->toBeNull() + ->and($mp3->height) + ->toBeNull() + ->and($mp3->duration) + ->toBe(67) + // wav + ->and($upload->audio_wav) + ->toBeTruthy() + ->toBeString() + ->toBeExists() + ->and($wav->width) + ->toBeNull() + ->and($wav->height) + ->toBeNull() + ->and($wav->duration) + ->toBe(67) + // flac + ->and($upload->audio_flac) + ->toBeTruthy() + ->toBeString() + ->toBeExists() + ->and($flac->width) + ->toBeNull() + ->and($flac->height) + ->toBeNull() + ->and($flac->duration) + ->toBe(67); +}); + +it('will generate audio styles correctly when secure-ids is enabled', function(LaruploadHeavyTestModel|LaruploadLightTestModel $model) { + config()->set('larupload.secure-ids', LaruploadSecureIdsMethod::ULID); + + $model = new ($model::class); + $model->setAttachments( + TestAttachmentBuilder::make($model->mode)->withWavAudio()->toArray() + ); + $model = save($model, mp3()); + + $attachment = $model->attachment('main_file'); + $id = $attachment->meta('id'); + $wav = urlToVideo($attachment->url('audio_wav')); + + expect(Str::isUlid($id))->toBeTrue() + // cover + ->and($attachment->url('cover')) + ->toBeNull() + // wav + ->and($attachment->url('audio_wav')) + ->toBeTruthy() + ->toBeString() + ->toBeExists() + ->and($wav->width) + ->toBeNull() + ->and($wav->height) + ->toBeNull() + ->and($wav->duration) + ->toBe(67); + +})->with('models'); diff --git a/tests/Feature/DownloadTest.php b/tests/Feature/DownloadTest.php index 6c03f48..e1e6fb1 100644 --- a/tests/Feature/DownloadTest.php +++ b/tests/Feature/DownloadTest.php @@ -62,6 +62,21 @@ })->with('models'); +it('will download custom audio styles', function(LaruploadHeavyTestModel|LaruploadLightTestModel $model) { + $model->setAttachments( + TestAttachmentBuilder::make($model->mode)->withWavAudio()->toArray() + ); + + $model = save($model, mp3()); + $attachment = $model->attachment('main_file'); + + expect($attachment->download('audio_wav')) + ->toBeInstanceOf(StreamedResponse::class) + ->getStatusCode() + ->toBe(200); + +})->with('models'); + it('will return null for styles that do not exist', function(LaruploadHeavyTestModel|LaruploadLightTestModel $model) { $model = save($model, jpg()); $attachment = $model->attachment('main_file'); diff --git a/tests/Feature/FFMpegQueueTest.php b/tests/Feature/FFMpegQueueTest.php index 5b4b403..dedbcee 100644 --- a/tests/Feature/FFMpegQueueTest.php +++ b/tests/Feature/FFMpegQueueTest.php @@ -1,7 +1,9 @@ set('larupload.ffmpeg.queue', true); }); -it('will process ffmpeg through queue', function() { - save(LaruploadTestModels::QUEUE->instance(), mp4()); +it('will process ffmpeg through queue', function(UploadedFile $file) { + Bus::assertNotDispatched(ProcessFFMpeg::class); + + save(LaruploadTestModels::QUEUE->instance(), $file); Bus::assertDispatched(ProcessFFMpeg::class); -}); -it('will process ffmpeg through queue in standalone mode', function() { +})->with([ + mp4(), + mp3() +]); + +it('will process ffmpeg through queue in standalone mode [video]', function() { + Bus::assertNotDispatched(ProcessFFMpeg::class); + Larupload::init('uploader') ->video('landscape', 400) ->upload(mp4()); - save(LaruploadTestModels::QUEUE->instance(), mp4()); + Bus::assertDispatched(ProcessFFMpeg::class); +}); + +it('will process ffmpeg through queue in standalone mode [audio]', function() { + Bus::assertNotDispatched(ProcessFFMpeg::class); + + Larupload::init('uploader') + ->audio('audio_wav', new Wav()) + ->upload(mp3()); Bus::assertDispatched(ProcessFFMpeg::class); }); @@ -56,6 +75,26 @@ expect($urls->landscape)->toBeExists(); }); +it('will create audio styles through queue process', function() { + $model = LaruploadTestModels::QUEUE->instance(); + $model = save($model, mp3()); + + $urls = $model->attachment('main_file')->urls(); + + expect($urls->original) + ->toBeExists() + ->and($urls->cover) + ->toBeNull() + ->and($urls->audio_wav) + ->toNotExists(); + + $queue = DB::table(Larupload::FFMPEG_QUEUE_TABLE)->first(); + $process = new ProcessFFMpeg($queue->id, $model->id, 'main_file', $model::class); + $process->handle(); + + expect($urls->audio_wav)->toBeExists(); +}); + it('will create video styles through queue process in standalone mode', function() { $name = 'uploader'; $standalone = Larupload::init($name)->video('landscape', 400); @@ -76,6 +115,26 @@ expect($uploader->landscape)->toBeExists(); }); +it('will create audio styles through queue process in standalone mode', function() { + $name = 'uploader'; + $standalone = Larupload::init($name)->audio('audio_wav', new Wav); + $uploader = $standalone->upload(mp3()); + + expect($uploader->original) + ->toBeExists() + ->and($uploader->cover) + ->toBeNull() + ->and($uploader->audio_wav) + ->toNotExists(); + + $queue = DB::table(Larupload::FFMPEG_QUEUE_TABLE)->first(); + $serializedClass = base64_encode(serialize($standalone)); + $process = new ProcessFFMpeg($queue->id, $uploader->meta->id, $name, Larupload::class, $serializedClass); + $process->handle(); + + expect($uploader->audio_wav)->toBeExists(); +}); + it('will create streams through queue process', function() { $model = LaruploadTestModels::QUEUE->instance(); $model = save($model, mp4()); @@ -121,7 +180,7 @@ }); -it('can queue ffmpeg for remote disks and deletes local files after finishing the process', function() { +it('can queue ffmpeg for remote disks and deletes local files after finishing the process', function(UploadedFile $file, int $expectedS3Files1, int $expectedS3Files2) { # init $disk = 's3'; $localDisk = config()->get('larupload.local-disk'); @@ -129,7 +188,7 @@ # save model $model = LaruploadTestModels::REMOTE_QUEUE->instance(); - $model = save($model, mp4()); + $model = save($model, $file); # prepare for assertions $queue = DB::table(Larupload::FFMPEG_QUEUE_TABLE)->first(); @@ -139,7 +198,7 @@ # assertions 1 expect($s3Files) - ->toHaveCount(2) + ->toHaveCount($expectedS3Files1) ->and($localFiles) ->toHaveCount(1) ->and($localFiles[0]) @@ -156,12 +215,15 @@ // assertions 2 expect($s3Files) - ->toHaveCount(7) + ->toHaveCount($expectedS3Files2) ->and($localFiles) ->toHaveCount(0); -}); +})->with([ + [mp4(), 2, 7], + [mp3(), 1, 2], +]); -it('can queue ffmpeg for remote disks and deletes local files after finishing the process in standalone mode', function() { +it('can queue ffmpeg for remote disks and deletes local files after finishing the process in standalone mode [video]', function() { # init $name = 'uploader'; $disk = 's3'; @@ -203,9 +265,58 @@ ->toHaveCount(8) ->and($localFiles) ->toHaveCount(0); + + Storage::disk($disk)->deleteDirectory('/'); +}); + +it('can queue ffmpeg for remote disks and deletes local files after finishing the process in standalone mode [audio]', function() { + # init + $name = 'uploader'; + $disk = 's3'; + $localDisk = config()->get('larupload.local-disk'); + Storage::fake($disk); + $standalone = Larupload::init($name) + ->disk($disk) + ->audio('audio_wav', new Wav); + + # upload + $uploader = $standalone->upload(mp3()); + + # prepare for assertions + $queue = DB::table(Larupload::FFMPEG_QUEUE_TABLE)->first(); + $serializedClass = base64_encode(serialize($standalone)); + $s3Files = Storage::disk($disk)->allFiles(); + $localFiles = Storage::disk($localDisk)->allFiles(); + + + # assertions 1 + expect($s3Files) + ->toHaveCount(2) + ->and($localFiles) + ->toHaveCount(1) + ->and($localFiles[0]) + ->toEndWith($uploader->meta->name); + + + # run queue + $process = new ProcessFFMpeg($queue->id, $uploader->meta->id, $name, Larupload::class, $serializedClass); + $process->handle(); + + # prepare for assertions + $s3Files = Storage::disk($disk)->allFiles(); + $localFiles = Storage::disk($localDisk)->allFiles(); + + // assertions 2 + expect($s3Files) + ->toHaveCount(3) + ->and($localFiles) + ->toHaveCount(0); + + + Storage::disk($disk)->deleteDirectory('/'); }); -it('can queue ffmpeg when using secure-ids', function() { +it('can queue ffmpeg when using secure-ids [video]', function() { config()->set('larupload.secure-ids', LaruploadSecureIdsMethod::ULID); $model = LaruploadTestModels::QUEUE->instance(); @@ -227,7 +338,29 @@ expect($urls->landscape)->toBeExists(); }); -it('can queue ffmpeg when using secure-ids in standalone mode', function() { +it('can queue ffmpeg when using secure-ids [audio]', function() { + config()->set('larupload.secure-ids', LaruploadSecureIdsMethod::ULID); + + $model = LaruploadTestModels::QUEUE->instance(); + $model = save($model, mp3()); + + $urls = $model->attachment('main_file')->urls(); + + expect($urls->original) + ->toBeExists() + ->and($urls->cover) + ->toBeExists() + ->and($urls->audio_wav) + ->toNotExists(); + + $queue = DB::table(Larupload::FFMPEG_QUEUE_TABLE)->first(); + $process = new ProcessFFMpeg($queue->id, $model->id, 'main_file', $model::class); + $process->handle(); + + expect($urls->audio_wav)->toBeExists(); +}); + +it('can queue ffmpeg when using secure-ids in standalone mode [video]', function() { config()->set('larupload.secure-ids', LaruploadSecureIdsMethod::ULID); $name = 'uploader'; @@ -273,9 +406,55 @@ expect($urls->landscape)->toBeExists(); }); -it('will change queue status after processing queue', function() { +it('can queue ffmpeg when using secure-ids in standalone mode [audio]', function() { + config()->set('larupload.secure-ids', LaruploadSecureIdsMethod::ULID); + + $name = 'uploader'; + $standalone = Larupload::init($name)->audio('audio_wav', new Wav); + $uploader = $standalone->upload(mp3()); + + expect($uploader->original) + ->toBeExists() + ->and($uploader->cover) + ->toBeExists() + ->and($uploader->audio_wav) + ->toNotExists(); + + $queue = DB::table(Larupload::FFMPEG_QUEUE_TABLE)->first(); + $serializedClass = base64_encode(serialize($standalone)); + $process = new ProcessFFMpeg($queue->id, $uploader->meta->id, $name, Larupload::class, $serializedClass); + $process->handle(); + + expect($uploader->audio_wav)->toBeExists(); + + + + + + + $model = LaruploadTestModels::QUEUE->instance(); - $model = save($model, mp4()); + $model = save($model, mp3()); + + $urls = $model->attachment('main_file')->urls(); + + expect($urls->original) + ->toBeExists() + ->and($urls->cover) + ->toBeExists() + ->and($urls->audio_wav) + ->toNotExists(); + + $queue = DB::table(Larupload::FFMPEG_QUEUE_TABLE)->first(); + $process = new ProcessFFMpeg($queue->id, $model->id, 'main_file', $model::class); + $process->handle(); + + expect($urls->audio_wav)->toBeExists(); +}); + +it('will change queue status after processing queue', function(UploadedFile $file) { + $model = LaruploadTestModels::QUEUE->instance(); + $model = save($model, $file); $queue = DB::table(Larupload::FFMPEG_QUEUE_TABLE)->first(); expect($queue) @@ -294,11 +473,15 @@ ->toBeObject() ->and($queue->status) ->toBe(1); -}); -it('will fire an event when process is finished', function() { +})->with([ + mp4(), + mp3(), +]); + +it('will fire an event when process is finished', function(UploadedFile $file) { $model = LaruploadTestModels::QUEUE->instance(); - $model = save($model, mp4()); + $model = save($model, $file); $queue = DB::table(Larupload::FFMPEG_QUEUE_TABLE)->first(); Event::assertNotDispatched(LaruploadFFMpegQueueFinished::class); @@ -307,11 +490,15 @@ $process->handle(); Event::assertDispatched(LaruploadFFMpegQueueFinished::class); -}); -it('will update queue record whit error message, when process failed', function() { +})->with([ + mp4(), + mp3(), +]); + +it('will update queue record whit error message, when process failed', function(UploadedFile $file) { $model = LaruploadTestModels::QUEUE->instance(); - $model = save($model, mp4()); + $model = save($model, $file); $queue = DB::table(Larupload::FFMPEG_QUEUE_TABLE)->first(); expect($queue->message)->toBeNull(); @@ -325,7 +512,10 @@ expect($queue->message)->toBeTruthy(); -})->throws(Exception::class); +})->throws(Exception::class)->with([ + mp4(), + mp3(), +]); it('can load queue relationships of model', function() { $model = LaruploadTestModels::QUEUE->instance(); @@ -350,7 +540,7 @@ $model = LaruploadTestModels::QUEUE->instance(); save($model, mp4()); - save($model, mp4()); - save($model, mp4()); + save($model, mp3()); + save($model, mp3()); })->throws(HttpResponseException::class); diff --git a/tests/Feature/ResponseTest.php b/tests/Feature/ResponseTest.php index 35b7126..dfbef4c 100644 --- a/tests/Feature/ResponseTest.php +++ b/tests/Feature/ResponseTest.php @@ -9,7 +9,9 @@ use Illuminate\Support\Facades\Storage; $properties = [ - 'original', 'cover', 'stream', 'small_size', 'small', 'medium', 'landscape', 'portrait', 'exact', 'auto', 'meta' + 'original', 'cover', 'stream', 'small_size', 'small', 'medium', + 'audio_mp3', 'audio_wav', 'audio_flac', + 'landscape', 'portrait', 'exact', 'auto', 'meta' ]; it('will return larupload object in toArray function', function(LaruploadHeavyTestModel|LaruploadLightTestModel $model) use ($properties) { diff --git a/tests/Feature/SecureIdsTest.php b/tests/Feature/SecureIdsTest.php index 9a8649a..97a8845 100644 --- a/tests/Feature/SecureIdsTest.php +++ b/tests/Feature/SecureIdsTest.php @@ -136,7 +136,7 @@ ->toBeExists(); }); -it('will work with ffmpeg class', function () { +it('will work with ffmpeg class [video]', function () { config()->set('larupload.secure-ids', LaruploadSecureIdsMethod::ULID); $model = LaruploadTestModels::HEAVY->instance(); @@ -165,5 +165,30 @@ ->and($attachment->url('landscape')) ->toContain($id) ->toBeExists(); +}); + +it('will work with ffmpeg class [audio]', function () { + config()->set('larupload.secure-ids', LaruploadSecureIdsMethod::ULID); + + $model = LaruploadTestModels::HEAVY->instance(); + $model->setAttachments( + TestAttachmentBuilder::make($model->mode)->withWavAudio()->toArray() + ); + + $model = save($model, mp3()); + + $attachment = $model->attachment('main_file'); + $id = $attachment->meta('id'); + expect(Str::isUlid($id))->toBeTrue() + ->and($attachment->url()) + ->toContain($id) + ->toBeExists() + // + ->and($attachment->url('cover')) + ->toBeNull() + // + ->and($attachment->url('audio_wav')) + ->toContain($id) + ->toBeExists(); }); diff --git a/tests/Feature/UploadEntityTest.php b/tests/Feature/UploadEntityTest.php index 174abaa..aa74f20 100644 --- a/tests/Feature/UploadEntityTest.php +++ b/tests/Feature/UploadEntityTest.php @@ -1,5 +1,7 @@ attachment->audio('audio_wav', new Wav); + $this->attachment->audio('audio_aac', new Aac()); + + $styles = $this->attachment->getAudioStyles(); + + expect($styles) + ->toBeArray() + ->toHaveCount(2) + ->and(array_keys($styles)) + ->toBe([ + 'audio_wav', 'audio_aac' + ]); +}); + it('can change dominant-color property', function() { $this->model->setAttachments([ $this->attachment->dominantColor(false) diff --git a/tests/Pest.php b/tests/Pest.php index 70186f7..5782073 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -109,6 +109,21 @@ function urlToVideo(string $url): FFMpegMeta return $ffmpeg->getMeta(); } +function urlToAudio(string $url): FFMpegMeta +{ + $baseUrl = url('/'); + $url = str_replace($baseUrl, '', $url); + $path = public_path($url); + + $fileName = pathinfo($path, PATHINFO_FILENAME); + $disk = config('larupload.disk'); + + $file = new UploadedFile($path, $fileName, null, null, true); + $ffmpeg = new FFMpeg($file, $disk, 10); + + return $ffmpeg->getMeta(); +} + function urlsToPath(AttachmentProxy $attachment, array $exclude = []): array { $paths = []; diff --git a/tests/Support/Models/LaruploadQueueTestModel.php b/tests/Support/Models/LaruploadQueueTestModel.php index 11ff1d9..43184e0 100755 --- a/tests/Support/Models/LaruploadQueueTestModel.php +++ b/tests/Support/Models/LaruploadQueueTestModel.php @@ -24,6 +24,7 @@ public function attachments(): array return TestAttachmentBuilder::make($this->mode) ->withLandscapeVideo() ->with480pStream() + ->withWavAudio() ->toArray(); } } diff --git a/tests/Support/Models/LaruploadRemoteQueueTestModel.php b/tests/Support/Models/LaruploadRemoteQueueTestModel.php index 7702a17..fbd4ed2 100755 --- a/tests/Support/Models/LaruploadRemoteQueueTestModel.php +++ b/tests/Support/Models/LaruploadRemoteQueueTestModel.php @@ -23,6 +23,7 @@ public function attachments(): array { return TestAttachmentBuilder::make($this->mode, 's3') ->withLandscapeVideo() + ->withWavAudio() ->with480pStream() ->toArray(); } diff --git a/tests/Support/Models/Traits/TestAttachments.php b/tests/Support/Models/Traits/TestAttachments.php index 05e9cc3..10a4bed 100755 --- a/tests/Support/Models/Traits/TestAttachments.php +++ b/tests/Support/Models/Traits/TestAttachments.php @@ -45,6 +45,13 @@ public function withAllVideos(): array ); } + public function withAllAudios(): array + { + return $this->setAttachments( + TestAttachmentBuilder::make($this->mode)->withAllAudios()->toArray() + ); + } + public function withStreams(): array { return $this->setAttachments( diff --git a/tests/Support/TestAttachmentBuilder.php b/tests/Support/TestAttachmentBuilder.php index e8b0445..0f418de 100755 --- a/tests/Support/TestAttachmentBuilder.php +++ b/tests/Support/TestAttachmentBuilder.php @@ -2,6 +2,10 @@ namespace Mostafaznv\Larupload\Test\Support; +use FFMpeg\Format\Audio\Aac; +use FFMpeg\Format\Audio\Flac; +use FFMpeg\Format\Audio\Mp3; +use FFMpeg\Format\Audio\Wav; use FFMpeg\Format\Video\X264; use Mostafaznv\Larupload\Enums\LaruploadMediaStyle; use Mostafaznv\Larupload\Enums\LaruploadMode; @@ -85,6 +89,36 @@ public function withAllImages(): self return $this; } + public function withMp3Audio(): self + { + $this->attachment = $this->attachment->audio('audio_mp3', new Mp3); + + return $this; + } + + public function withWavAudio(): self + { + $this->attachment = $this->attachment->audio('audio_wav', new Wav); + + return $this; + } + + public function withFlacAudio(): self + { + $this->attachment = $this->attachment->audio('audio_flac', new Flac); + + return $this; + } + + public function withAllAudios(): self + { + $this->withMp3Audio() + ->withWavAudio() + ->withFlacAudio(); + + return $this; + } + public function withSmallSizeVideo(): self { $this->attachment = $this->attachment->video('small_size', 200, 200, LaruploadMediaStyle::CROP); @@ -199,7 +233,7 @@ public function withAllVideosAndStreams(): self public function withAll(): self { - $this->withAllImages()->withAllVideosAndStreams(); + $this->withAllImages()->withAllVideosAndStreams()->withAllAudios(); return $this; } diff --git a/tests/Unit/AudioStyleTest.php b/tests/Unit/AudioStyleTest.php new file mode 100644 index 0000000..a2e5088 --- /dev/null +++ b/tests/Unit/AudioStyleTest.php @@ -0,0 +1,10 @@ +throws(Exception::class, 'Style name [12] is numeric. please use string name for your style'); + diff --git a/tests/Unit/FFMpegTest.php b/tests/Unit/FFMpegTest.php index 5dc92a0..188ffc5 100644 --- a/tests/Unit/FFMpegTest.php +++ b/tests/Unit/FFMpegTest.php @@ -1,12 +1,16 @@ throws(Exception::class); +it('can manipulate audios', function(AudioStyle $style, string $fileName, int $bitrate, string $codec) { + $path = get_larupload_save_path('local', $fileName)['local']; + + expect(file_exists($path))->toBeFalse(); + + $ffmpeg = new FFMpeg(mp3(), 'local', 10); + $ffmpeg->audio($style, $fileName); + + expect(file_exists($path))->toBeTrue(); + + $file = new UploadedFile($path, $fileName, null, null, true); + $audio = new FFMpeg($file, 'local', 10); + $meta = $audio->getMeta(); + + + expect($meta->width) + ->toBeNull() + ->and($meta->height) + ->toBeNull() + ->and($meta->duration) + ->toBe(67) + ->and($audio->getMedia()->getStreams()->first()->isAudio()) + ->toBeTrue() + ->and((int)$audio->getMedia()->getStreams()->first()->get('bit_rate')) + ->toBe($bitrate) + ->and($audio->getMedia()->getStreams()->first()->get('codec_type')) + ->toBe('audio') + ->and($audio->getMedia()->getStreams()->first()->get('codec_name')) + ->toBe($codec); + + @unlink($path); + +})->with([ + fn() => [ + 'style' => AudioStyle::make('mp3', (new Mp3())->setAudioKiloBitrate(32)), + 'file_name' => 'audio.mp3', + 'bit_rate' => 32000, + 'codec' => 'mp3' + ], + fn() => [ + 'style' => AudioStyle::make('wav', new Wav()), + 'file_name' => 'audio.wav', + 'bit_rate' => 705600, + 'codec' => 'pcm_s16le' + ], + fn() => [ + 'style' => AudioStyle::make('wav', new Flac()), + 'file_name' => 'audio.flac', + 'bit_rate' => 0, + 'codec' => 'flac' + ], +]); + + +it('can guess correct file extension based on audio-style', function(AudioStyle $style, string $fileName) { + $path = get_larupload_save_path('local', $fileName)['local']; + + expect(file_exists($path))->toBeFalse(); + + $ffmpeg = new FFMpeg(mp3(), 'local', 10); + $ffmpeg->audio($style, 'audio.ext'); + + expect(file_exists($path))->toBeTrue(); + + @unlink($path); + +})->with([ + fn() => [ + 'style' => AudioStyle::make('mp3', new Mp3()), + 'file_name' => 'audio.mp3', + ], + fn() => [ + 'style' => AudioStyle::make('wav', new Wav()), + 'file_name' => 'audio.wav', + ], + fn() => [ + 'style' => AudioStyle::make('wav', new Flac()), + 'file_name' => 'audio.flac', + ], +]); + +it('can upload manipulated audios to remote disks', function() { + $disk = 's3'; + Storage::fake($disk); + + $ffmpeg = new FFMpeg(mp3(), $disk, 10); + $ffmpeg->audio( + style: AudioStyle::make('wav', new Wav()), + saveTo: 'audio.wav' + ); + + $files = Storage::disk($disk)->allFiles(); + + expect($files) + ->toBeArray() + ->toHaveCount(1) + ->toMatchArray([ + 'audio.wav' + ]); +}); + +it('can convert video to audio', function() { + $fileName = 'audio.mp3'; + $path = get_larupload_save_path('local', $fileName)['local']; + + $style = AudioStyle::make('mp3', new Mp3()); + + $ffmpeg = new FFMpeg(mp4(), 'local', 10); + $ffmpeg->audio($style, $fileName); + + expect(file_exists($path))->toBeTrue(); + + $file = new UploadedFile($path, $fileName, null, null, true); + $audio = new FFMpeg($file, 'local', 10); + $meta = $audio->getMeta(); + + expect($meta->width) + ->toBeNull() + ->and($meta->height) + ->toBeNull() + ->and($meta->duration) + ->toBe(5) + ->and($audio->getMedia()->getStreams()->first()->isAudio()) + ->toBeTrue() + ->and($audio->getMedia()->getStreams()->first()->get('codec_type')) + ->toBe('audio') + ->and($audio->getMedia()->getStreams()->first()->get('codec_name')) + ->toBe('mp3'); + + @unlink($path); +}); + it('can manipulate videos', function(VideoStyle $style, int $width, int $height) { $fileName = 'video.mp4'; $path = get_larupload_save_path('local', $fileName)['local']; diff --git a/tests/Unit/FixExceptionNamesActionTest.php b/tests/Unit/FixExceptionNamesActionTest.php new file mode 100644 index 0000000..a62a161 --- /dev/null +++ b/tests/Unit/FixExceptionNamesActionTest.php @@ -0,0 +1,42 @@ +run(); + + expect($res)->toBe($path); + +})->with([ + Larupload::ORIGINAL_FOLDER, + Larupload::COVER_FOLDER +]); + +it('will convert svg to jpg for custom styles', function() { + $res = FixExceptionNamesAction::make('path/to/file.svg', 'custom-style')->run(); + + expect($res)->toBe('path/to/file.jpg'); +}); + +it('will change file extension based on style', function() { + $style = AudioStyle::make('custom-style', new Aac()); + $res = FixExceptionNamesAction::make('path/to/file.mp3', $style->name, $style)->run(); + + expect($res)->toBe('path/to/file.aac'); +}); + +it('wont change file extension if style doesnt have custom extension', function() { + $style = ImageStyle::make('custom-style', 100, 100); + $path = 'path/to/file.png'; + $res = FixExceptionNamesAction::make($path, $style->name, $style)->run(); + + expect($res)->toBe($path); +}); + diff --git a/tests/Unit/UtilsTest.php b/tests/Unit/UtilsTest.php index 3b230c2..4e27bcc 100644 --- a/tests/Unit/UtilsTest.php +++ b/tests/Unit/UtilsTest.php @@ -85,6 +85,45 @@ enum TestEnum ]); }); +it('can generate save path with custom extension', function() { + $path = 'path/to/file.png'; + $newPath = 'path/to/file.jpg'; + $localPath = config('filesystems.disks.local.root'); + + $result = get_larupload_save_path('local', $path, 'jpg'); + + expect($result) + ->toBeArray() + ->toHaveCount(5) + ->toMatchArray([ + 'path' => 'path/to', + 'name' => 'file.jpg', + 'temp' => null, + 'local' => $localPath . '/' . $newPath, + 'permanent' => $localPath . '/' . $newPath, + ]); + + + $carbon = Carbon::createFromFormat('Y-m-d H:i:s', '1990-09-20 07:10:48'); + $time = $carbon->unix(); + testTime()->freeze($carbon); + + $result = get_larupload_save_path('s3', $path, 'jpg'); + + $tempPath = larupload_temp_dir(); + + expect($result) + ->toBeArray() + ->toHaveCount(5) + ->toMatchArray([ + 'path' => 'path/to', + 'name' => 'file.jpg', + 'temp' => $tempPath . "/$time-file.jpg", + 'local' => $tempPath . "/$time-file.jpg", + 'permanent' => $newPath, + ]); +}); + it('can upload file to remote disks', function() { $driver = 's3'; $file = pdf(); @@ -171,3 +210,24 @@ enum TestEnum file_is_valid(png(2), 'file', 'cover'); })->throws(RuntimeException::class); + + +it('will return path unchanged if extension is null', function() { + $original = 'path/to/file.mp3'; + $path = larupload_style_path($original, null); + + expect($path)->toBe($original); +}); + +it('will return path unchanged if extension is an empty string', function() { + $original = 'path/to/file.mp3'; + $path = larupload_style_path($original, ''); + + expect($path)->toBe($original); +}); + +it('will change path using the given extension', function() { + $path = larupload_style_path('path/to/file.mp3', 'wav'); + + expect($path)->toBe('path/to/file.wav'); +});