[PHP] On-the-fly MP3 genereren

Pagina: 1
Acties:

Vraag


Acties:
  • 0 Henk 'm!

  • Koenvh
  • Registratie: December 2011
  • Laatst online: 11-10 00:28

Koenvh

Hier tekenen: ______

Topicstarter
Goedendag iedereen,

Ik ben bezig met een PHP website die muziek vanaf WebDAV kan streamen (een beetje à la Spotify), en nu loop ik tegen een probleem aan.

Niet alle bestandstypen werken met HTML5 audio, eigenlijk is er maar één bestandstype is wat door alle browser wordt ondersteund, namelijk MP3.
Toch zijn er genoeg mensen die ook muziek in een ander formaat hebben, en om ook dat te laten werken wil ik de bestanden converteren.

Ik maak gebruik van de SabreDAV client om de bestanden op te halen, waarna ik de content tijdelijk opsla, om het vervolgens te converteren met ffmpeg en de content terug te sturen.

Nu duurt dit proces met mijn server (RPi2 :P ) nogal lang, vooral omdat eerst het hele bestand geconverteerd moet worden.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...

header("Content-Transfer-Encoding: binary");

if($response["headers"]["content-type"][0] == "audio/mpeg"){
    header('Content-Type: audio/mpeg');
    echo $response["body"];
} else {
    //header('Content-Type: audio/mpeg');
    //echo $response["body"];
    //die();

    //Generate a random name:
    $randomString = substr(str_shuffle("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 1) . substr(md5(time()), 1);
    file_put_contents(CONVERT_FOLDER . "/" . $randomString . "", $response["body"]);
    shell_exec(FFMPEG . " -i " . CONVERT_FOLDER . "/" . $randomString . " -threads auto -aq 3 -vn " . CONVERT_FOLDER . "/" . $randomString . ".mp3");
    header('Location: ' . CONVERT_FOLDER_RELATIVE . "/" . $randomString . ".mp3");
    die();
}


Toen zag ik op StackOverflow een andere oplossing, namelijk met passthru:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
header("Content-Transfer-Encoding: binary");

if($response["headers"]["content-type"][0] == "audio/mpeg"){
    header('Content-Type: audio/mpeg');
    echo $response["body"];
} else {
    header('Content-Type: audio/mpeg');
    //echo $response["body"];
    //die();

    //Generate a random name:
    $randomString = substr(str_shuffle("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 1) . substr(md5(time()), 1);
    file_put_contents(CONVERT_FOLDER . "/" . $randomString . "", $response["body"]);
    passthru(FFMPEG . " -i " . CONVERT_FOLDER . "/" . $randomString . " -threads auto -aq 3 -vn -f mp3 -");

    exit();
    //header('Location: ' . CONVERT_FOLDER_RELATIVE . "/" . $randomString . ".mp3");
    //die();
}


En dat werkt sneller, maar heeft als nadeel dat seek niet meer werkt omdat van tevoren de lengte niet opgegeven is.

Nu is de vraag: Hoe zorg ik dat seek wel werkt? Of is er nog een andere, snellere oplossing?
Alvast bedankt :)

EDIT:
Met de Content-Length header krijg ik de seek wel, maar hoe bepaal ik die voor de MP3 die nog gegenereerd moet worden?

🠕 This side up

Alle reacties


Acties:
  • +1 Henk 'm!

  • Patriot
  • Registratie: December 2004
  • Laatst online: 00:15

Patriot

Fulltime #whatpulsert

Waarom zet je de bestanden niet direct om bij het uploaden (waarbij je het origineel desnoods opslaat voor later)? Dat lijkt me een handigere oplossing, en het veroorzaakt minder load op je server omdat het niet iedere keer on-the-fly moet worden geconvert als iemand een bestand opvraagt dat geen mp3 is.

EDIT:
Ok, als ik zo de namen van de variabelen nog eens goed bekijk heb ik het donkerbruine vermoeden dat je de bestanden zelf pas in je bezit krijgt op het moment van de request (ik vermoed dat je ze gewoon dan pas download?).

Je hebt dan een aantal mogelijkheden, maar daarvan komt het meeste neer op dat je de bestanden eerder moet hebben. Als je het bestand echt pas kunt hebben op het moment van request, dan is de enige resterende oplossing om het resultaat van de functie te cachen. Je moet de naam dus niet meer baseren op een random string, maar deze baseren op bijvoorbeeld de naam van het opgevraagde externe bestand. Dan heb je alleen de allereerste keer dat dat specifieke bestand wordt opgevraagd geen seek.

[ Voor 54% gewijzigd door Patriot op 27-04-2016 03:23 ]


Acties:
  • 0 Henk 'm!

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
Gooi een md5-hash over de bestandsnaam (of content) heen, bij de 1e die hem helemaal afcodeert sla je onder die md5 de lengte op.

Dan voordat je begint te spelen kijk je of je een md5 hebt staan om als lengte te versturen, zonee dan heeft de gebruiker pech.

Acties:
  • 0 Henk 'm!

  • DJMaze
  • Registratie: Juni 2002
  • Niet online
In de metadata van bestanden kan de lengte (speelduur) opgeslagen zijn.
Bij MP3 bestanden is dat de ID3v2 tag.
Voor bestanden met een CBR kan je dat ook zelf exact uitrekenen en bij VBR bestanden is dat bij benadering.

Maak je niet druk, dat doet de compressor maar


Acties:
  • 0 Henk 'm!

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
DJMaze schreef op woensdag 27 april 2016 @ 09:23:
In de metadata van bestanden kan de lengte (speelduur) opgeslagen zijn.
Bij MP3 bestanden is dat de ID3v2 tag.
Voor bestanden met een CBR kan je dat ook zelf exact uitrekenen en bij VBR bestanden is dat bij benadering.
Je hebt echter niet de speelduur nodig (nou ok, die heb je ook nodig, maar die komt in de 1e paar bits voorbij), maar de lengte van het bestand wat je gaat oversturen...

En de lengte van het bestand is pas bekend als je het gegenereerd hebt (je zou hem in theorie kunnen schatten, maar als je er 1 bit naastzit dan gaat je browser al eeuwig laden aan het einde)

Acties:
  • 0 Henk 'm!

  • Koenvh
  • Registratie: December 2011
  • Laatst online: 11-10 00:28

Koenvh

Hier tekenen: ______

Topicstarter
Patriot schreef op woensdag 27 april 2016 @ 03:07:
Waarom zet je de bestanden niet direct om bij het uploaden (waarbij je het origineel desnoods opslaat voor later)? Dat lijkt me een handigere oplossing, en het veroorzaakt minder load op je server omdat het niet iedere keer on-the-fly moet worden geconvert als iemand een bestand opvraagt dat geen mp3 is.

EDIT:
Ok, als ik zo de namen van de variabelen nog eens goed bekijk heb ik het donkerbruine vermoeden dat je de bestanden zelf pas in je bezit krijgt op het moment van de request (ik vermoed dat je ze gewoon dan pas download?).

Je hebt dan een aantal mogelijkheden, maar daarvan komt het meeste neer op dat je de bestanden eerder moet hebben. Als je het bestand echt pas kunt hebben op het moment van request, dan is de enige resterende oplossing om het resultaat van de functie te cachen. Je moet de naam dus niet meer baseren op een random string, maar deze baseren op bijvoorbeeld de naam van het opgevraagde externe bestand. Dan heb je alleen de allereerste keer dat dat specifieke bestand wordt opgevraagd geen seek.
Correct. Via WebDAV haal ik de lijst met bestanden op, en pas als de gebruiker een bestand kiest begint het converteren. Veel eerder kan ik 'm niet hebben, tenzij ik bij voorbaat alles converteer, maar dan verplaats ik de storage van (in dit geval mijn STACK/ownCloud) naar mijn webserver, en dat is niet helemaal de bedoeling :P
Gomez12 schreef op woensdag 27 april 2016 @ 05:36:
Gooi een md5-hash over de bestandsnaam (of content) heen, bij de 1e die hem helemaal afcodeert sla je onder die md5 de lengte op.

Dan voordat je begint te spelen kijk je of je een md5 hebt staan om als lengte te versturen, zonee dan heeft de gebruiker pech.
Zoiets had ik ook al in gedachten, maar dan is er een tweede probleem: Controleren of bestand a nog steeds inhoud x heeft. (al kan ik hiervoor wel iets als filesize gebruiken). "Dan heeft de gebruiker pech" vind ik een prima instelling, met uitzondering dat ook ik een gebruiker ben :+ :P

Ik ga eens kijken of ik de md5 hash van de bestandsnaam kan gebruiken om het tweede keer laden te versnellen. Daarnaast zal ik ook kijken of ik mijn Raspberry Pi 2 iets kan overclocken. Ik kan dan alleen geen passthru meer gebruiken omdat die de raw data teruggeeft aan PHP ipv dat die het wegschrijft naar de schijf. (Tenzij ik dat weer naar de schijf schrijf, maar dat lijkt me enigszins dubbelop)

Qua ffmpeg-configuratie: Is daar nog snelheidswinst in te behalen?

🠕 This side up


Acties:
  • 0 Henk 'm!

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
Koenvh schreef op woensdag 27 april 2016 @ 15:57:
[...]
Zoiets had ik ook al in gedachten, maar dan is er een tweede probleem: Controleren of bestand a nog steeds inhoud x heeft. (al kan ik hiervoor wel iets als filesize gebruiken).
Dan genereer je de md5 dus over de content van de file...
Qua ffmpeg-configuratie: Is daar nog snelheidswinst in te behalen?
Daar zou ik niet al te veel naar kijken, inherent ga je dan namelijk aan je kwaliteit klooien...

Dan zou ik eerder kijken of je de plek waar je de md5's opslaat niet ook publiekelijk toegankelijk kan maken.

Want je hebt in wezen toegang tot je mp3'jes, dus dan zou ik eerder denken aan dat je van je proces een afgeleide maakt dat je niets afspeelt maar enkel converteert en de length / md5 berekent.
Dan kan je dat proces namelijk op je gewone desktop draaien waardoor je separaat de boel kan voorrekenen (want je desktop/laptop zal x keer sneller zijn dan je rpi) en dan op je rpi kan opslaan.

Dan kan je periodiek gewoon een heel zooitje bestanden vooruit behandelen.

Het enige wat je even goed moet nakijken is of ffmpeg wel elke keer hetzelfde bestand genereert (met dezelfde lengte) en dat er niet ergens iets randoms inzit wat net 1 of 2 bytes scheelt per keer transcoden.

[ Voor 8% gewijzigd door Gomez12 op 27-04-2016 19:59 ]


Acties:
  • 0 Henk 'm!

  • Koenvh
  • Registratie: December 2011
  • Laatst online: 11-10 00:28

Koenvh

Hier tekenen: ______

Topicstarter
Gomez12 schreef op woensdag 27 april 2016 @ 19:58:
[...]

Dan genereer je de md5 dus over de content van de file...


[...]

Daar zou ik niet al te veel naar kijken, inherent ga je dan namelijk aan je kwaliteit klooien...

Dan zou ik eerder kijken of je de plek waar je de md5's opslaat niet ook publiekelijk toegankelijk kan maken.

Want je hebt in wezen toegang tot je mp3'jes, dus dan zou ik eerder denken aan dat je van je proces een afgeleide maakt dat je niets afspeelt maar enkel converteert en de length / md5 berekent.
Dan kan je dat proces namelijk op je gewone desktop draaien waardoor je separaat de boel kan voorrekenen (want je desktop/laptop zal x keer sneller zijn dan je rpi) en dan op je rpi kan opslaan.

Dan kan je periodiek gewoon een heel zooitje bestanden vooruit behandelen.

Het enige wat je even goed moet nakijken is of ffmpeg wel elke keer hetzelfde bestand genereert (met dezelfde lengte) en dat er niet ergens iets randoms inzit wat net 1 of 2 bytes scheelt per keer transcoden.
Kan natuurlijk ook :P

Van tevoren converteren is natuurlijk het makkelijkst, ik kan ook alleen maar MP3'tjes op m'n ownCloud gooien, maar helaas: Het moet uiteindelijk een systeem worden welke voor iedereens WebDAV share werkt, en niet alleen die van mij. Ik heb dus geen invloed over de inhoud, en ik kan dus ook niet alvast dingen converteren. Daarnaast moet de RPi niet de nieuwe opslagplek worden voor alle media, daar is m'n ownCloud nou juist voor :+

🠕 This side up


Acties:
  • 0 Henk 'm!

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
Koenvh schreef op woensdag 27 april 2016 @ 23:52:
[...]
Kan natuurlijk ook :P

Van tevoren converteren is natuurlijk het makkelijkst, ik kan ook alleen maar MP3'tjes op m'n ownCloud gooien, maar helaas: Het moet uiteindelijk een systeem worden welke voor iedereens WebDAV share werkt, en niet alleen die van mij. Ik heb dus geen invloed over de inhoud, en ik kan dus ook niet alvast dingen converteren. Daarnaast moet de RPi niet de nieuwe opslagplek worden voor alle media, daar is m'n ownCloud nou juist voor :+
Je leest het verkeerd, ik bedoel niet dat je alles van te voren moet converteren, dan loop je tegen de problemen aan die je zelf al geeft :)

Alleen ik ben egoïstisch en ik wil voor mezelf het beste hebben (wat hierbij is dat in ieder geval je eigen bestanden al van te voren gechecked zijn) dus een parallel proces wat je eigen bestanden lijkt mij niet verkeerd, je eigen nog niet behandelde bestanden gaan dan volgens het standaard procédé dat ze na 1 keer goed benaderbaar zijn net zoals bestanden van ieder ander ter wereld, alleen je eigen wel behandelde bestanden die gaan op keer 0 al goed...

Acties:
  • 0 Henk 'm!

  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
Gomez12 schreef op woensdag 27 april 2016 @ 11:55:
[...]
En de lengte van het bestand is pas bekend als je het gegenereerd hebt (je zou hem in theorie kunnen schatten, maar als je er 1 bit naastzit dan gaat je browser al eeuwig laden aan het einde)
Dan plak je er toch wat dummy ID3 tags aan vast? De MP3 decoder aan de andere kant checkt toch de file lengte niet, die gebruikt de MP3 headers.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


Acties:
  • 0 Henk 'm!

  • DJMaze
  • Registratie: Juni 2002
  • Niet online
Koenvh schreef op woensdag 27 april 2016 @ 23:52:
Het moet uiteindelijk een systeem worden welke voor iedereens WebDAV share werkt, en niet alleen die van mij. Ik heb dus geen invloed over de inhoud, en ik kan dus ook niet alvast dingen converteren.
Je hoeft er ook geen invloed op te hebben, je kan gewoon de WebDAV filteren op *.mp3 en als men zich afvraagd waarom kan je dat melden. Dan zetten ze zelf de bestanden maar om.
Zie http://hpr.dogphilosophy.net/test/

Zie ook http://audiocogs.org/articles/2012/06/15/flac-and-aurora/

[ Voor 6% gewijzigd door DJMaze op 28-04-2016 09:49 ]

Maak je niet druk, dat doet de compressor maar


Acties:
  • 0 Henk 'm!

  • Koenvh
  • Registratie: December 2011
  • Laatst online: 11-10 00:28

Koenvh

Hier tekenen: ______

Topicstarter
DJMaze schreef op donderdag 28 april 2016 @ 09:43:
[...]

Je hoeft er ook geen invloed op te hebben, je kan gewoon de WebDAV filteren op *.mp3 en als men zich afvraagd waarom kan je dat melden. Dan zetten ze zelf de bestanden maar om.
Zie http://hpr.dogphilosophy.net/test/

Zie ook http://audiocogs.org/articles/2012/06/15/flac-and-aurora/
Ook mogelijk, maar echt gebruiksvriendelijk is het niet. Aurora werkt wel, maar het probleem met de speler die ik gebruik (jPlayer) is dat of aurora wordt gebruikt (en mp3 niet meer werkt) of html5 (en flac niet meer werkt)

🠕 This side up


Acties:
  • 0 Henk 'm!

  • DJMaze
  • Registratie: Juni 2002
  • Niet online
jPlayer werkt niet goed bij mij. De progress bar stuitert alle kanten op.
Ga je support leveren aan alle gebruikers?

Maak je niet druk, dat doet de compressor maar


Acties:
  • 0 Henk 'm!

  • Koenvh
  • Registratie: December 2011
  • Laatst online: 11-10 00:28

Koenvh

Hier tekenen: ______

Topicstarter
DJMaze schreef op donderdag 28 april 2016 @ 12:47:
jPlayer werkt niet goed bij mij. De progress bar stuitert alle kanten op.
Ga je support leveren aan alle gebruikers?
Da's vreemd, hier geen last van. Welk browser/OS? In Edge merk ik wel dat de seek bar niet echt mooi vooruit gaat, maar dat is op te lossen door smoothPlayBar: false.

Support, voor zover dat kan natuurlijk wel, maar geen contract of iets dergelijks :P

🠕 This side up


Acties:
  • 0 Henk 'm!

  • DJMaze
  • Registratie: Juni 2002
  • Niet online
Fedora 22 KDE, Firefox 45.0

Maak je niet druk, dat doet de compressor maar


Acties:
  • 0 Henk 'm!

  • Koenvh
  • Registratie: December 2011
  • Laatst online: 11-10 00:28

Koenvh

Hier tekenen: ______

Topicstarter
DJMaze schreef op donderdag 28 april 2016 @ 13:34:
[...]

Fedora 22 KDE, Firefox 45.0
Hier met Windows 10 en Firefox 47.0b1 en Linux Mint 17.1 met Firefox 45.0.2 werkt het prima, dus ik verwacht ook geen problemen voor Fedora 22 met Firefox 45.0 :)

🠕 This side up


Acties:
  • 0 Henk 'm!

  • CH4OS
  • Registratie: April 2002
  • Niet online

CH4OS

It's a kind of magic

Hou er rekening mee, dat de MP3 codec een gesloten codec is, wat onderhevig is aan licenties.

Acties:
  • 0 Henk 'm!

  • Koenvh
  • Registratie: December 2011
  • Laatst online: 11-10 00:28

Koenvh

Hier tekenen: ______

Topicstarter
CptChaos schreef op donderdag 28 april 2016 @ 14:32:
Hou er rekening mee, dat de MP3 codec een gesloten codec is, wat onderhevig is aan licenties.
Weet ik, maar:
However, no license is needed for private, non-commercial activities (e.g., home-entertainment, receiving broadcasts and creating a personal music library), not generating revenue or other consideration of any kind or for entities with associated annual gross revenue less than US$ 100 000.00.
en dit valt onder niet-commercieel.

Overigens zou m'n enige andere optie dan wav zijn, maar dan ondersteunt IE het niet meer.

Toch jammer dat er niet een oplossing is om dit proces te versnellen, al verbaast het me niet echt.
Toch bedankt :) Mocht iemand benieuwd zijn naar het resultaat: Koenvh in "[Alg] Welke tools heb jij gemaakt? - deel IV"

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$requestURL = $_GET["file"];
$md5name = md5($requestURL);
if(file_exists(CONVERT_FOLDER . "/" . $md5name . ".mp3") == false) {
    $response = $client->request('GET', $requestURL);

    header("HTTP/1.0 " . $response["statusCode"]);
    //header('Content-Type: audio/mpeg');
    //header('Content-Disposition: filename="'. end(explode('/', $requestURL)) . '"');
    header('Content-length: ' . $response["headers"]["content-length"][0]);
    header('Cache-Control: no-cache');
    header("Content-Transfer-Encoding: binary");


    if ($response["headers"]["content-type"][0] == "audio/mpeg") {
        file_put_contents(CONVERT_FOLDER . "/" . $md5name . ".mp3", $response["body"]);
    } else {
        file_put_contents(CONVERT_FOLDER . "/" . $md5name . "", $response["body"]);
        shell_exec(FFMPEG . " -i " . CONVERT_FOLDER . "/" . $md5name . " -threads auto -aq 3 -vn " . CONVERT_FOLDER . "/" . $md5name . ".mp3");
    }
}
header('Location: ' . CONVERT_FOLDER_RELATIVE . "/" . $md5name . ".mp3");
die();


Bij het laden van de pagina wordt een ander script aangeroepen die alle bestanden ouder dan een uur verwijdert.

[ Voor 3% gewijzigd door Koenvh op 28-04-2016 22:15 ]

🠕 This side up


Acties:
  • 0 Henk 'm!

  • Koenvh
  • Registratie: December 2011
  • Laatst online: 11-10 00:28

Koenvh

Hier tekenen: ______

Topicstarter
Mocht iemand anders nog op zoek zijn naar iets meer efficiëntie, ik heb nu de volgende functie toegevoegd:

JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function determineSupportedAudio() {
    var mimeTypes = {
        "audio/mpeg": false,
        "audio/x-mpeg": false,
        "audio/ogg": false,
        "audio/x-vorbis+ogg": false,
        "audio/webm": false,
        "audio/wav": false,
        "audio/x-wav": false,
        "audio/aac": false,
        "audio/flac": false
    };
    var aud = document.createElement('audio');
    for (var key in mimeTypes) {
        if (!mimeTypes.hasOwnProperty(key)) continue;
        if (aud.canPlayType(key) == "probably" || aud.canPlayType(key) == "maybe"){
            mimeTypes[key] = true;
        }
    }
    //console.log(mimeTypes);
    return mimeTypes;
}

Dit object stuur ik dan mee, waarna PHP controleert of het opgehaalde mimetype ondersteund wordt. Hiermee wordt bijvoorbeeld WAV niet geconverteerd voor Firefox en Chrome, maar wel voor IE.

🠕 This side up

Pagina: 1