Skip to content

Commit

Permalink
Add QuickTime format
Browse files Browse the repository at this point in the history
  • Loading branch information
mantas-done committed Feb 22, 2020
1 parent 56bf440 commit 45c8c5b
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 4 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Convert and edit subtitles and captions.
| Advanced Sub Station | .ass |
| [DFXP](https://en.wikipedia.org/wiki/Timed_Text_Markup_Language) | .dfxp |
| [TTML](https://en.wikipedia.org/wiki/Timed_Text_Markup_Language) | .ttml |
| QuickTime | .qt.txt |

## Installation
```
Expand Down Expand Up @@ -164,7 +165,7 @@ You can contribute in any way you want. If you need some guidance, choose someth

| Task | Difficulty | Description |
| --- | --- | --- |
| Add new formats | Medium | Supporting more formats is nice. Some popular formats: .mcc, .ttml, .qt.txt, .dfxp, .cap |
| Add new formats | Medium | Supporting more formats is nice. Some popular formats: .mcc, .cap |
| Refactor [StlConverter.php](https://github.com/mantas783/subtitle-converter/blob/master/src/code/Converters/StlConverter.php) file | Easy | .stl format is very similar to .srt. The only problem is that StlConverter.php code can be simplified a lot (check [SrtConverter.php](https://github.com/mantas783/subtitle-converter/blob/master/src/code/Converters/SrtConverter.php) as example) |
| Add .scc format | Hard | [Format description](https://en.wikipedia.org/wiki/EIA-608) |

Expand Down
82 changes: 82 additions & 0 deletions src/code/Converters/TxtConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php namespace Done\Subtitles;

// qt.txt
class TxtConverter implements ConverterContract {

private static $fps = 23;

public function fileContentToInternalFormat($file_content)
{
$internal_format = [];

$blocks = explode("\n\n", trim($file_content));
foreach ($blocks as $block) {
preg_match('/(?<start>\[.{11}\])\n(?<text>[\s\S]+?)(?=\n\[)\n(?<end>\[.{11}\])/m', $block, $matches);

// if block doesn't contain text
if (empty($matches)) {
continue;
}

$internal_format[] = [
'start' => static::timeToInternal($matches['start'], self::$fps),
'end' => static::timeToInternal($matches['end'], self::$fps),
'lines' => explode("\n", $matches['text']),
];
}

return $internal_format;
}

/**
* Convert library's "internal format" (array) to file's content
*
* @param array $internal_format Internal format
* @return string Converted file content
*/
public function internalFormatToFileContent(array $internal_format)
{
$file_content = '{QTtext} {font:Tahoma}
{plain} {size:20}
{timeScale:30}
{width:160} {height:32}
{timestamps:absolute} {language:0}
';

foreach ($internal_format as $block) {
$start = static::fromInternalTime($block['start'], self::$fps);
$end = static::fromInternalTime($block['end'], self::$fps);
$lines = implode("\r\n", $block['lines']);

$file_content .= $start . "\r\n";
$file_content .= $lines . "\r\n";
$file_content .= $end . "\r\n";
$file_content .= "\r\n";
}

return $file_content;
}

// ------------------------------ private --------------------------------------------------------------------------

protected static function timeToInternal($srt_time, $fps)
{
$parsed = date_parse("1970-01-01 $srt_time UTC");
$time = $parsed['hour'] * 3600 + $parsed['minute'] * 60 + $parsed['second'] + $parsed['fraction'] / $fps * 100;

return $time;
}

protected static function fromInternalTime($internal_time, $fps)
{
$parts = explode('.', $internal_time);
$whole = $parts[0]; // 1
$decimal = isset($parts[1]) ? substr($parts[1], 0, 2) : 0;

$frame = round($decimal / 100 * 24);

$srt_time = gmdate("H:i:s", floor($whole)) . '.' . str_pad($frame, 2, '0', STR_PAD_LEFT);

return "[$srt_time]";
}
}
48 changes: 48 additions & 0 deletions tests/formats/TxtTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

use Done\Subtitles\Subtitles;
use PHPUnit\Framework\TestCase;

class TxtTest extends TestCase {

use AdditionalAssertions;

private $qttxt = "{QTtext} {font:Tahoma}
{plain} {size:20}
{timeScale:30}
{width:160} {height:32}
{timestamps:absolute} {language:0}
[00:02:17.11]
Senator, we're making
our final approach into Coruscant.
[00:02:20.09]
[01:02:20.11]
Very good, Lieutenant.
[01:02:22.12]
";

public function testConvertingToFormat()
{
$actual = (new Subtitles())
->add(137.44, 140.375, ['Senator, we\'re making', 'our final approach into Coruscant.'])
->add(3740.476, 3742.501, ['Very good, Lieutenant.'])
->content('txt');

$this->assertEquals($this->qttxt, $actual);
}

public function testConvertingToInternalFormat()
{
$actual = Subtitles::load($this->qttxt, 'txt')->getInternalFormat();

$expected = (new Subtitles())
->add(137.44, 140.375, ['Senator, we\'re making', 'our final approach into Coruscant.'])
->add(3740.476, 3742.501, ['Very good, Lieutenant.'])
->getInternalFormat();

$this->assertInternalFormatsEqual($expected, $actual, 0.07);
}

}
6 changes: 3 additions & 3 deletions tests/helpers/AdditionalAssertions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

trait AdditionalAssertions
{
public function assertInternalFormatsEqual($expected, $actual)
public function assertInternalFormatsEqual($expected, $actual, $allowable_error = 0.01)
{
foreach ($expected as $k => $block) {
$this->assertTrue(round($expected[$k]['start'], 2) === round($actual[$k]['start'], 2), round($expected[$k]['start'], 2) . ' vs ' . round($actual[$k]['start'], 2));
$this->assertTrue(round($expected[$k]['end'], 2) === round($actual[$k]['end'], 2), round($expected[$k]['end'], 2) . ' vs ' . round($actual[$k]['end'], 2));
$this->assertTrue(abs(round($expected[$k]['start'], 2) - round($actual[$k]['start'], 2)) < $allowable_error, round($expected[$k]['start'], 3) . ' vs ' . round($actual[$k]['start'], 3));
$this->assertTrue(abs(round($expected[$k]['end'], 2) - round($actual[$k]['end'], 2)) < $allowable_error, round($expected[$k]['end'], 3) . ' vs ' . round($actual[$k]['end'], 3));

foreach ($expected[$k]['lines'] as $line_k => $line) {
$this->assertEquals($line, $actual[$k]['lines'][$line_k]);
Expand Down

0 comments on commit 45c8c5b

Please sign in to comment.