<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Phase 7.5 &mdash; Audio and Video in Pure PHP &mdash; Arc Plan</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 760px;
    margin: 1.5em auto; padding: 0 1em; line-height: 1.5; color: #1a1a1a; }
h1 { font-size: 1.4em; }
h2 { font-size: 1.1em; margin-top: 1.4em; }
ol { margin: 0.3em 0; } li { margin: 0.25em 0; }
ol.outline { list-style-type: upper-roman; }
ol.outline ol { list-style-type: upper-alpha; }
ol.outline ol ol { list-style-type: decimal; }
ol.outline ol ol ol { list-style-type: lower-alpha; }
.check { color: #1a7f37; font-weight: bold; }
.next { color: #b35900; font-weight: bold; }
.review { color: #0969da; font-weight: bold; }
.working { color: #b35900; font-weight: bold; }
.nope { color: #82071e; font-weight: bold; }
.q { color: #8250df; font-weight: bold; }
code { background: #f2f2f2; padding: 0 0.25em; border-radius: 3px; }
em { color: #555; }
</style>
</head>
<body>
<h1>Phase 7.5 &mdash; Audio and Video in Pure PHP</h1>
<p><em>Markers: <span class="next">&#9654;</span> being worked on,
<span class="done">&#10003;</span> implemented and waiting to be reviewed,
<span class="check">&#10003;</span> done,
<span class="check">&#10003;</span><span class="nope">&#10007;</span> closed
by deciding not to do it, <span class="q">?</span> open decision,
unmarked = planned.</em></p>

<h2>Arc Post-Mortem Summary</h2>
<p>This arc gave Yioop its own audio and video, then put the code that had
grown around them in order. Yioop reads Vorbis, Opus, FLAC and the
container formats in PHP alone, records a message in the browser and plays
it back without ffmpeg, and a page's resource folder recodes what is put
in it (I, II). The library was grouped into folders by what its classes
are for (III), and the four files over five thousand lines were split:
WikiElement gave up its git pages, GroupModel became a base with a wiki
model and a feed model under it, and SocialComponent fell from 16,932
lines to 5,215 as the base that WikiComponent, FeedComponent,
ResourceComponent, MessageComponent, MailComponent and GitComponent
extend (IV).</p>
<p>Two rules came out of that splitting and are now in the process file. A
component never calls another component: what two of them need sits in the
base they extend, is protected rather than private, and takes its
constants with it. And the home of every method is settled before anything
moves, because moving whatever the last measurement named makes new
crossings on each pass. The faults this arc left for Chris to find were
all of one shape, a name that moved out from under a caller, and each is
now a reading a patch cannot pass: every call on a model, every call on a
component, every name a test asks of one.</p>
<p>The arc closed on the screens themselves. The wiki editor's preview
stamps each line of the source it drew, so the line the caret sits on can
be put in the middle of the preview rather than the two panes being lined
up by a ratio of their heights. A wiki link may name the language of the
page it wants, and a resource marker the folder its file sits in, both
read as markup rather than as part of a name.</p>
<h2>What this arc is for</h2>
<p>Yioop reaches for ffmpeg to make a thumbnail from a video, to work out
how long one runs, and to make over a recorded message. That means a site
cannot do any of it without a program installed beside it, and a site that
has one runs somebody else's program on files that arrived from the web.
Two pure-PHP libraries written elsewhere do both jobs, and this arc brings
them in, wires them where ffmpeg is reached for today, and gives them one
command-line front end (I). It then settles what a page's resource folder
should do about recoding what is dropped into it (II). With the library
folder about to gain a good many files, it groups that folder into
subfolders (III), and closes by looking for the places the code has grown
past what a reader can hold (IV).</p>

<ol class="outline">
<li><span class="done">&#10003;</span> <strong>Bring the pure-PHP audio
and video libraries into
        Yioop.</strong> Both were written in another conversation. They go
        under <code>src/library/av_processing</code>, in a namespace of
        that name. Each gets one command line front end and Yioop's own
        unit tests. <ol>
        <li><span class="done">&#10003;</span> <strong>Split VideoThumb.php
        into twenty-three files the autoloader can find.</strong> The file
        held fifty-eight classes in nineteen thousand lines. They run from
        bit readers to whole decoders for five video formats. Every class
        another file reaches for now has a file named after it, so nothing
        requires anything. Names are built from English words as Coding.pdf
        asks, and a docblock says what its subject is for. Checked by
        decoding. A ten second MP4 and a five second WebM each give a
        thumbnail at the width asked for.</li>
<li><span class="done">&#10003;</span> <strong>Move its command line into
        AVTool.php.</strong> AVTool.php holds the help text and the option
        handler. It makes a picture from a video, and turns a recording into
        an MP4 every browser plays.
        It refuses to run from a web request. Which half runs is settled by
        the file's first bytes, not its name. Twelve cases run it and the
        audio class as a person would.</li>
<li><span class="done">&#10003;</span> <strong>Put the audio library under
        the same folder and namespace.</strong> Thirty-one files came in.
        They are readers for Ogg and WebM, a decoder for Opus, an encoder, and
        a writer for the sound file. They sit beside
        the video work under the arc's namespace, and the loader they
        shipped with is dropped. AudioConverter turns a recording into an
        MP4 a few seconds at a time, and PlainSound reads WAV and AIFF.
        Any kind the folder reads converts to any kind it writes. Reading
        Vorbis, MP3 and AAC is not written yet,
        and those
        are refused with a sentence saying so.</li>
<li><span class="done">&#10003;</span> <strong>Rewrite its tests as Yioop
        unit tests.</strong> Sixteen test files came in, with five small
        recordings and three helper classes. The runner and the test class the
        library shipped
        with are dropped, since Yioop has both. Each test names the classes by
        the arc's namespace and finds its recordings where Yioop keeps them.
        They run as they stand:
        131 cases pass under <code>CodeTool.php unit</code>. Two run for about
        a hundredth of a second, which is real decoding rather than a fault.
        Each says so in its own docblock.</li>
<li><span class="done">&#10003;</span> <strong>A video's thumbnail and
        its running time come from the library.</strong> Two places asked
        ffmpeg for a picture and ffprobe for a running time. Both read
        the file with VideoExtractor now. A thumbnail is made where no
        ffmpeg is set at all. Measured on a five second WebM: 5.01
        seconds, 320 by 240, a still picture in a tenth of a second. Both
        thumbnails are WebP now, the moving one named .animated.webp. A served
        file's type is read from its name rather than its bytes.</li>
<li><span class="done">&#10003;</span> <strong>Say where ffmpeg is still
        reached for.</strong> Seven files name it, counting the setting
        FFMPEG and the plain word separately. Two are replaced by this
        arc, two should be replaced later, and three stay. The audit
        below says which is which and why. <ol>
        <li><span class="done">&#10003;</span>
        <strong>Replaced by this arc.</strong> GroupModel's
        convertRecordingToMp4 runs ffmpeg to make a recording over into
        mp4; that is item H. UsermessagesElement offers the record button
        only where FFMPEG is set, and stops gating on it once H lands.
        </li>
        <li><span class="done">&#10003;</span>
        <strong>Should be replaced later.</strong> VideoConvertJob cuts an
        uploaded video into short segments with ffmpeg and stitches them back.
        It also recodes avi or mov to mp4. Its
        thumbnail step is replaced already. PodcastDownloadJob stitches
        podcast segments and now names ffmpeg by the setting. Both are
        background jobs run by the media updater.</li>
        <li><span class="done">&#10003;</span>
        <strong>Stays.</strong> Config.php declares the setting
        itself. PublicHelpPages tells a reader how to install ffmpeg.
        GroupModel schedules an uploaded video for recoding behind the
        setting. It makes a voice message over with the audio library, which
        is item H.</li>
        <li><span class="done">&#10003;</span>
        <strong>Gates removed.</strong> Three places asked whether FFMPEG
        was set before work that no longer needs it. They are a video page's
        size and running time, a video's thumbnail, and VideoConvertJob's
        thumbnail step. All three read the file
        with the library now.</li>
        </ol></li>
<li><span class="done">&#10003;</span> <strong>Write unit tests for the
        video side.</strong> The video library
        arrives with none. Each container reader and each decoder gets cases
        that read a small file of that kind. They check the frame that comes
        back, so one decoder cannot break another unseen.</li>
<li><span class="done">&#10003;</span> <strong>A recorded message is
        made over without ffmpeg.</strong> Saving a recording turns
        whatever arrived into one kind, by handing the file to ffmpeg.
        That path uses the audio library instead. A site with no ffmpeg
        can then still take a voice message. A browser records the way
        built for speech, which Yioop could not decode, so that decoder
        is the work below. <ol>
        <li><span class="done">&#10003;</span> <strong>Read the opening
        of a stretch of speech.</strong> The marks at the front of a
        packet now read, one for each stretch. The kind of sound and the
        loudness of each quarter read as well. The tables come from the
        counts the standard fixes. Each is walked the way the range
        decoder walks a table.</li>
        <li><span class="done">&#10003;</span> <strong>Read the shape a
        stretch was spoken with.</strong> A stretch names a guess from a
        fixed book and mends each of its sixteen frequencies. Those
        frequencies now read, and are pushed apart where two sit too
        close. Every shape of the ten second sample comes back rising in
        order, which any mistake in that arithmetic would break.</li>
        <li><span class="done">&#10003;</span> <strong>Read the pitch of
        a voiced stretch.</strong> How far back the sound repeats now
        reads, along with a filter of five taps for each quarter. The
        distance is carried from one stretch to the next, since a
        stretch may write it as a move. Half the jumps between
        neighboring stretches are three samples. That is a voice moving
        four cycles a second.</li>
        <li><span class="done">&#10003;</span> <strong>Read the pulses
        of a stretch.</strong> The counts, the splitting and the signs
        all read now. Against the reference decoder, built here and made
        to print what it reads, the two agree sample for sample. That is
        198 packets of speech and 63,360 samples. Two faults were found this
        way. A block is split one half all the way down before the other, and
        the sign sets run seven apart.</li>
        <li><span class="done">&#10003;</span> <strong>Turn shape,
        pitch and pulses back into samples.</strong> The scale each
        quarter is played at now comes out of the loudness numbers. All
        four scales of all 198 packets match the reference decoder. The
        frequencies, the filter terms and the rough sound all match on every
        packet. Every packet agrees with the reference decoder, sample for
        sample. <ol>
        <li><span class="done">&#10003;</span> <strong>A stretch says how
        much of the earlier sound to lean on.</strong> A stretch standing on
        its own writes one of three
        answers before its pulses. That answer now reads, which is the
        first piece of carrying sound between stretches.</li>
        <li><span class="done">&#10003;</span> <strong>A voiced stretch
        reaches into the sound before it.</strong> The samples already played
        are run backward through the shape filter. They are then put at the
        pitch filter's scale.
        The five taps of a pitch filter are held with seven bits of fraction.
        The filter works with fourteen, so each is shifted up. The rough sound
        of a voiced quarter sits within a percent of the reference. It had no
        relation at all before.</li>
        <li><span class="done">&#10003;</span> <strong>The whole chain runs
        from a file to samples.</strong>
        SpeechDecoder walks a recording and keeps what one stretch
        leaves the next. Ten seconds decodes in a fifth of a second.
        AVTool writes the result as a wave file with --speech. Against
        the reference decoder it agrees sample for sample: 198 packets
        and 63,360 samples.</li>
        </ol></li>
        <li><span class="done">&#10003;</span> <strong>Carry a stretch's
        state into the next one.</strong> SpeechDecoder keeps the
        filters, the loudness and the samples already played across a
        packet boundary. A packet may carry a spare copy of a stretch
        for a listener who lost the one before. That copy is read past
        so the stretch behind it is found, then thrown away.</li>
        <li><span class="done">&#10003;</span> <strong>Give the samples to
        the converter.</strong> AudioConverter sends a speech recording
        to the new decoder rather than refusing it. The samples are
        raised to forty-eight thousand a second, the rate Opus plays
        at, by weighing four samples either side. A browser writes its
        recording in parts, and its pieces are found from the heading
        of each part. GroupModel makes a voice message over with this
        converter, so a site without ffmpeg can take one.</li>
        
        </ol></li>
    </ol></li>
<li><span class="done">&#10003;</span> <strong>Settle what a page's
        resource folder does about recoding.</strong> A file dropped into
        a wiki page's folder is
        stored as it arrived, apart from recordings, which are made over.
        What should happen for video, and for audio that is already in the
        kind Yioop wants, is not settled. <ol> <li><span
        class="done">&#10003;</span>
        <strong>Decide what <code>copyFileToGroupPageResource</code>
        should recode.</strong> A file carrying pictures is stored as it
        arrived. Sound alone is made over into the one kind every browser
        plays. The file itself is read rather than its name or the kind
        it claims, since a browser recording a voice names itself a video
        kind.</li>
<li><span class="done">&#10003;</span> <strong>Read
        VideoConvertJob and PodcastDownloadJob against that
        decision.</strong> VideoConvertJob is deleted. Its only work came
        from marker files GroupModel wrote for an uploaded video. That
        block is gone, so a video is stored like any other file.
        PodcastDownloadJob keeps its own use of the
        convert folder and still names ffmpeg by the setting.</li>
    </ol></li>
<li><strong>Group src/library into subfolders.</strong> The folder holds
        files of every kind side by side, and this arc adds a hundred more.
        Related files are gathered into subfolders so a reader can find
        their way, with the namespaces and every reference following. <ol>
        <li><span class="done">&#10003;</span> <strong>Say what the groups
        are.</strong> A pass over the folder
        naming which files belong together, so the moving is one decision
        rather than a hundred.</li>
<li><span class="done">&#10003;</span> <strong>A refused add user
        form comes back holding what was typed.</strong> Manage Users
        sent the writer to an empty form when the two passwords differed.
        The five boxes that are not passwords travel with the refusal, so
        the form opens again holding them. A password is not among them,
        since it does not belong in an address. The browser compares the
        two password boxes before the form is sent, so a mismatch costs no
        trip to the server. Where the server does the refusing, the add
        user row stays open.</li>
        <li><span class="done">&#10003;</span> <strong>Expanding a group's
        members leaves the reader looking at them.</strong> The members
        count on a group's settings page reloads the page with the list
        shown. The reload left the reader at the foot of the page rather
        than at the list. The Membership fieldset now carries an id and
        the link ends with it. The browser lands on that section with its
        legend in view.</li>
        <li><span class="done">&#10003;</span> <strong>The server says how
        much memory it holds against the requests it has served.</strong>
        A figure that climbs on its own does not say whether the climbing
        comes from work done or from time passing. Every five minutes a
        line now reports the growth, the requests served between two
        lines, and the bytes each. A steady figure per request says
        something is held between requests. A falling one says memory is
        claimed once and reused.</li>
        <li><span class="done">&#10003;</span> <strong>A git object too
        large to rebuild is refused rather than fatal.</strong>
        Rebuilding an object from a delta holds the old and new versions
        at once and grows the new one by doubling. That comes to about
        two and a half times the object, measured at 218 megabytes for a
        90 megabyte rebuild. The ceiling of 512 megabytes therefore
        allowed an object the server could not carry, and seekquarry.com
        died with a memory fatal. It is now 192 megabytes.</li>
        <li><span class="done">&#10003;</span> <strong>Move the files and
        follow
the references.</strong> Fifty files
moved into six folders. Each gained its group's namespace. A moved class
is named six ways and each was followed. Those are a name written in full,
the L alias, and a name built out of NS_LIB. Also a callback, a __DIR__
load, and a loose library function. A file naming a group takes an alias
for it, such as LW for the wiki group. The root index.php is followed too,
since it sits outside src.</li>
    </ol></li>
<li><strong>Look for the places the code has grown past reading.</strong>
        Yioop has methods and files long enough that a reader cannot hold
        them, and passages repeated in several places. This section finds
        them and says which are worth taking apart. <ol>
        <li><span class="done">&#10003;</span> <strong>Find
        the methods over five hundred lines.</strong> A list of them and what
        each does. For each, whether it splits along a line a reader would
        know. <ol>
        <li><span class="done">&#10003;</span> <strong>The edit page form
        of the wiki view is split, 1,093 lines to 493.</strong>
        renderEditPageForm, split by fieldset.</li>
        <li><span class="done">&#10003;</span> <strong>The group feed
        activity is split, 1,070 lines to 130.</strong> groupFeeds, one
        method an arm of its switch: toggleThreadMail, addComment,
        addFeedContact, addFeedGroup, copyOrCutPost, deletePost,
        deleteClip, emptyClip, pasteAllPosts, pastePost, flagPost,
        approveFlagged, deleteFlagged, downVotePost, startNewThread,
        feedStatus, updatePost, upVotePost.</li>
        <li><span class="done">&#10003;</span> <strong>The wiki activity
        is split, 930 lines to 189.</strong> wiki, into
        cleanWikiRequestFields, deleteWikiPage, wikiHistory, wikiPageList,
        wikiRelationships, wikiSource, addWikiHistoryPage,
        addWikiReadPage, initializeWikiEditAndSource.</li>
        <li><span class="done">&#10003;</span> <strong>The wiki editor
        activity is split, 906 lines to 198.</strong> editWiki, into
        editWikiResourceFile, editWikiHeadVars, editWikiPageSettings,
        editWikiResourceAction, editWikiUploadResource.</li>
        <li><span class="done">&#10003;</span> <strong>The manage groups
        activity is split, 820 lines to 323.</strong> manageGroups, one
        method an arm of its switch: activateOrPromoteUsers,
        addUserToGroup, banGroupUser, createGroup, deleteGroup,
        deleteGroupUser, groupSettings, infoGroup, changeMailPreference,
        inviteGroupUsers, joinGroup, groupGraphStats, setMemberAccess,
        setPostLifetime, setVoteAccess, setRegisterType, groupStatistics,
        unsubscribeFromGroup.</li>
        <li><span class="done">&#10003;</span> <strong>The search sources
        activity is split, 646 lines to 263.</strong> searchSources, one
        method an arm of its switch: addMediaSource, addSubsearch,
        clearSourceData, deleteMediaSource, deleteSubsearch,
        editSubsearch, editMediaSource, searchMediaSources,
        searchSubsearches, testMediaSource.</li>
        <li><span class="done">&#10003;</span> <strong>The page options
        activity is split, 627 lines to 277.</strong> pageOptions, into
        pageOptionsCrawlSettings and pageOptionsTestPage. This one has no
        switch: it is a run of blocks, and the two taken out are the
        settling of what a crawl will run with and the trying of one page
        through the processors.</li>
        <li><span class="done">&#10003;</span> <strong>The manage users
        activity is split, 565 lines to 172.</strong> manageUsers, one
        method an arm of its switch: addUser, editUser, actAsUser,
        deleteUser, addUserRole, addUserGroup, deleteUserRole,
        deleteUserGroup, searchUsers, updateUserStatus.</li>
        </ol></li>
<li><strong>Put back the methods that were never worth making, and take
out the passages that are.</strong> A method under five lines called
fewer than three times, and a method under ten lines called fewer than
twice, buys a reader nothing: its body goes back where it was called
from. A passage over five lines standing in three places or more is the
opposite case, and wants a method of its own. Each with what it does and
where it should live. <ol>
        <li><span class="done">&#10003;</span> <strong>Forty-one methods
        nothing can reach are taken away.</strong> Together they held
        1,043 lines. devlog/check_orphans.sh names a method only
        where its name appears nowhere else in the source at all, since a
        name is reached in ways one line does not show: IndexShard's
        makeWords is written inside a longer string and called through
        it, and MailSite declares userExists and getPasswordHash for the
        class under library/mail to answer for. Both were taken away by a
        looser rule first and stopped the test run.</li>
        <li><span class="done">&#10003;</span> <strong>The classifier
        tool's seven methods are put back.</strong> They were taken away
        as unreachable, but its main builds the name of what to run out
        of what the command line said, so run{$activity} reaches
        runTrainAndTest and that reaches the other six. The check now
        reads the fronts and backs joined into a name that is called
        through a variable, so ArcTool's outputInfo and this run are both
        known. An advertisement comparator is put back too, and the check
        leaves what handles advertisements and credits alone, since a
        script people buy calls into it from outside this source.</li>
        <li><span class="done">&#10003;</span> <strong>Methods nothing
        can reach are taken away.</strong> Sixty-two of them, near five
        thousand lines, from a tokenizer that ran another language&rsquo;s
        interpreter to the deprecated index shard&rsquo;s slice readers.
        A method only a test called went with its case, since such a case
        stands for a caller that is not there.</li>
        <li><span class="done">&#10003;</span> <strong>A short method
        called once or twice goes back where it was called.</strong>
        Ninety of them. The name bought a reader nothing: it had to be
        looked up to learn what the one line did. Each was moved by hand
        with the file read back after it, and where a test named one, the
        case now holds what the method held.</li>
        <li><span class="done">&#10003;</span> <strong>What may move is
        decided by what the body reaches.</strong> A body reaching
        anything private or protected stays where it is, since the caller
        may not reach it; so does one naming its own class, one whose
        file the caller does not import from, one that writes a file or
        reaches the database, a view drawing a screen, and an empty hook
        a subclass fills.</li>
        <li><span class="done">&#10003;</span> <strong>The reading that
        decides what can be reached is made to match how Yioop calls
        things.</strong> A name built at run time, a name inside a
        string, a name spelt with different case, a name only a comment
        holds, and the root index.php all count now. Each was added after
        a method it had missed was taken away and something broke: the
        media updater, the server&rsquo;s timer, the classifier tool.</li>
        <li><span class="done">&#10003;</span> <strong>The dev scripts
        move into devlog/devscripts.</strong> check_git_clone.sh and
        drive_screens.js are gone with the browser scripts that had
        replaced the second, and every path inside the remaining scripts
        and in the process file points at the new folder.</li>
        <li><span class="done">&#10003;</span> <strong>The wiki editor's
        page settings are read again.</strong> Splitting editWiki lifted
        the reading of them into editWikiHeadVars and left nothing
        calling it, so every setting a writer picked was dropped on save.
        editWikiPageSettings calls it now. Two cases pin what it reads,
        and check_orphans.sh is what found it.</li>
        </ol></li>
<li><strong>Find the files over five thousand lines.</strong> Eight
        are that long. Five are set aside: the three atto servers are
        another project's copy, PublicHelpPages.php is generated, and
        H264Decoder.php holds fifteen classes that belong together. The
        three that remain are Yioop's own, and they are read below by
        what each method is about and by how often one subject's methods
        call another's. <ol>
        <li><span class="done">&#10003;</span> <strong>The git pages
        move out of the wiki view.</strong> Twenty-three methods and
        1,178 lines drawing a repository, its readme, its history, its
        statistics and its issues become GitElement, and WikiElement
        falls from 5,770 lines to 4,594. The wiki view reaches the new
        element by name through the view helper at the four places it
        drew a git page itself.</li>
        <li><span class="done">&#10003;</span> <strong>The group model
        becomes a base with two children.</strong> GroupModel held
        groups, wiki pages and the feed in 6,793 lines. It now holds
        groups and their members in 2,623 lines. WikiModel holds the
        pages, their settings, their resources and the git issues.
        FeedModel holds the threads and posts. Both extend GroupModel,
        neither extends the other, and the base calls neither. The two
        page lookups both children need stayed in the base. Callers ask
        the model that holds what they want, which took 298 call sites
        across twenty files.</li>
        <li><span class="done">&#10003;</span> <strong>Mail moves out of
        the social component.</strong> The 55 methods that read and
        write a mail account, its mailboxes and its messages become
        MailComponent, 3,723 lines, and SocialComponent falls from
        16,932 lines to 13,358. Four calls cross the seam and each goes
        through the controller. The userMail activity moves to the new
        component in AdminController and GroupController, or the
        activity is not found at run time. Twenty-three tests on the
        request argument become three lists and one built method
        name.</li>
        <li><span class="done">&#10003;</span> <strong>The wiki moves
        out of the social component.</strong> Its 53 wiki methods and the
        21 git issue methods, about 6,250 lines with their support,
        become WikiComponent, and what stays is the feed, groups,
        contacts, ballots and bots at about 6,000. The activity wiki
        moves to the new component in AdminController, GroupController
        and ApiController. As built, five components came out of
        SocialComponent rather than one: the wiki, the group feed, the
        files kept with a page, the messages between two users and the
        git pages, and the base fell to 5,215 lines. The home of every
        method is settled first from what calls it, since moving
        whatever the last measurement named makes new crossings each
        pass. A method two components need sits in the base and is
        protected rather than private, and the constants a moved method
        reads move with it.</li>
        </ol></li>
    </ol></li>
<li><strong>Other work accomplished, oldest first.</strong> Each step below
was reached while working on something above. It is kept here so the arc says
what happened. <ol>
        <li><span class="done">&#10003;</span> <strong>A paste of resources
        lands in the language being looked at.</strong> A page has a copy
        in each language, each with resources of its own. The links out of
        the resources page dropped the language. A paste made on the
        Vietnamese page went to the English one. Every
        link from that page now carries it.</li>
        <li><span class="done">&#10003;</span> <strong>A public page
        shows its group's header and footer.</strong> A group's owner sets a
        header page, a footer page and a theme once for the group. The
        group side of the site filled these into a page's head. The static
        side did not, so a p address showed neither. It fills them the
        same way now. A page names its own only where the group allows
        it.</li>
        <li><span class="done">&#10003;</span> <strong>A locale named in
        an address reaches the page lookup.</strong> Yioop's own web
        server answers many requests in one process. The locale was held
        in a static that outlived a request. The first request settled the
        locale for every request after it, so l in a later address was
        ignored. RequestScope keeps a value in $_SERVER, which that server
        saves and puts back around every handover. A value there acts like
        a static but belongs to one request. The locale is kept there, and
        so is the list of names this machine answers to.</li>
        <li><span class="done">&#10003;</span> <strong>Either part of a
        choosing list scrolls on its own.</strong> The chosen items and
        the rest now sit in a list of their own inside the outer one.
        A part holding more than seven items scrolls by itself. A long
        list of chosen items no longer pushes the rest off the screen.
        With nine items each, both parts stand 384 pixels tall over 642
        pixels of items. With two and three, neither scrolls.</li>
        <li><span class="done">&#10003;</span> <strong>A double click
        carries an item across the choosing line.</strong> A wiki form's
        choosek list has a line dividing what is chosen from the rest.
        Dragging across it is awkward on a long list. A double click below
        the line sends an item to the top of the chosen. One above it goes
        to the top of the rest.</li>
        <li><span class="done">&#10003;</span> <strong>Read the sound a
        media file carries, in PHP alone.</strong> Yioop takes the sound out
        of a video and reads plain sound files. It decodes the kinds a browser
        sends. Each part below is measured against
        ffmpeg's decoding of the same file. <ol>
        <li><span class="done">&#10003;</span> <strong>Take the sound
        track out of a video file.</strong> SoundTrack finds the sound of
        an MP4, WebM or Ogg file without decoding a picture. Naming a
        video and a sound file writes that sound on its own. Five seconds
        of a WebM gave 240,960 samples as a WAV.</li>
        <li><span class="done">&#10003;</span> <strong>Read and write the
        sound files that keep their samples as they are.</strong>
        PlainSound reads and writes WAV and AIFF. A file's kind is settled
        by its first bytes rather than its name. Sound passes through a
        couple of seconds at a time. A four minute song converts with no
        large file beside it.</li>
        <li><span class="done">&#10003;</span> <strong>Decode AAC, the
        sound an MP4 usually carries.</strong> AacDecoder reads a frame
        part by part and all eleven tables of codes. Every one of
        ffmpeg's ninety-five frames for a tone decodes. A stereo song
        differs from ffmpeg by six thousandths a sample.</li>
        <li><span class="done">&#10003;</span> <strong>Decode Vorbis, the
        sound an Ogg file usually carries.</strong> VorbisSetup reads the
        tables and VorbisDecoder turns packets into samples. Twenty
        seconds of a song differs from ffmpeg by one ten-thousandth a
        sample, with no shifting. The codes a book uses are worked out
        the way this format asks, which is not the usual way.</li>
        <li><span class="drop">&#10007;</span>
        <strong>Decode MP3.</strong> MP3 needs machinery of its own. That
        is a bank of filters, the side information beside each frame, and
        their tables. None of it is shared with the work
        already here.</li>
        <li><span class="drop">&#10007;</span> <strong>Write Vorbis and MP3,
        not only read them.</strong> An
        encoder for each would let a caller ask for either kind. Yioop
        needs neither today, so both wait.</li>
        </ol></li>
        <li><span class="done">&#10003;</span> <strong>Mend how a media
        list draws its rows.</strong> Rows were drawn one over another
        and the count on a folder was painted over by it. The head of the
        list is closed whether or not a heading row is written. A cell is
        as tall as its row, and the count has a place of its own.
        Measured on a media list raised in the sandbox: rows at 98, 166,
        234 and 302.</li>
        <li><span class="done">&#10003;</span> <strong>Draw a document's
        cover the way the page itself draws it.</strong> A thumbnail is
        drawn by walking the page's own drawing. Four magazine covers
        went from 98, 15, 95 and 33 to 13.7, 13.2, 12.4 and 6.7 of 255.
        That measure is against another reader's rendering of the same
        page.</li>
        <li><span class="done">&#10003;</span> <strong>A blob read asks
        only for what the file holds.</strong> The live server died
        asking for a string of 278 megabytes while near its limit. A
        read takes its length from an index and asks for all of it
        before reading. A wrong length asks for that much whatever the
        file holds. The length is cut to what is left in the file. One
        past 32 megabytes is refused, with a log line naming the file
        and offset.</li>
        <li><span class="done">&#10003;</span> <strong>A stored video keeps
        its pictures.</strong> Every file put in a page's resource folder
        was offered to the voice message converter. It decided by the
        file's ending. A downloaded show named .mp4 was taken for a
        recording and written over with its sound alone. The converter
        now reads the file: one carrying pictures is left as it
        arrived.</li>
        <li><span class="done">&#10003;</span> <strong>A podcast item is
        fetched with ffmpeg where it names the pieces of a
        video.</strong> downloadPodcastItem read such a playlist itself,
        chose a quality, fetched the pieces and joined them. ffmpeg does
        all of that from the playlist's address alone, so 173 lines came
        out for 124. A file that is not a playlist is fetched as a page
        with no ffmpeg run on it. The pieces are copied as they
        stand and nothing is written again. ffmpeg's progress reaches the
        log or the terminal every five seconds. Making a thumbnail for
        such a video asked a smoothing table for a column it does not
        hold, which stopped the media updater.</li>
        <li><span class="done">&#10003;</span> <strong>A picture file gets
        its own thumbnail drawn.</strong> A thumbnail is kept under the
        file's whole name with .webp added. The one for a_picture.webp is
        a_picture.webp.webp. The controller left the ending off where a
        name already carried it. That asked for the file itself and drew
        nothing. It adds the ending wherever such a file stands
        beside it, and leaves the bare name only where none does.</li>
        <li><span class="done">&#10003;</span> <strong>Choosing a language
        keeps the screen a writer is on.</strong> The language dropdown
        built an address ending arg=edit and nothing else. A writer
        looking at a page's resources landed on its text opened for
        editing. It keeps what is being looked at now, read from the
        same two names the rest of the view reads. A redirect keeps the
        language too, so a paste comes back on the copy it was made
        on.</li>
        <li><span class="done">&#10003;</span> <strong>The emoji picker
        drops the descriptions that overrode a screen reader.</strong>
        Each emoji carried a short name a writer types and a description,
        and the description became a label. A reader's own software names
        an emoji in the reader's own language, so the label replaced that
        with one language for everybody. The 1,823 descriptions are gone
        from 25 locales. The array moved out of the constructor into a
        method of its own.</li>
        <li><span class="done">&#10003;</span> <strong>Two long view
        methods are split by fieldset.</strong> ServersElement::render
        drew six fieldsets in one method of 1,096 lines, and
        ManagegroupsElement::renderGroupsForm drew five in 601. Each
        fieldset is now a method named for the help button already in it.
        A fieldset works out for itself what it needs about the form,
        since a value the old method held is not there once the fieldset
        moves out.</li>
        <li><span class="done">&#10003;</span> <strong>The wiki editor is
        split into four methods.</strong> renderEditPageForm was 1,093
        lines. It is now 493, with renderEditSettings, renderEditBody and
        renderEditButtons beside it. The thirteen facts its markup reads
        throughout became properties first, since a stretch cannot move
        while those are locals. Five names ending in a kind were renamed
        with them. The whole page, compared byte for byte on all three
        screens, differs only where the token differs.</li>
        <li><span class="done">&#10003;</span> <strong>The group feed's
        switch becomes eighteen methods.</strong> groupFeeds ran to 1,070
        lines because every thing a reader can do to a feed sat in one
        switch. Each arm is now a method named for what it does, and the
        switch chooses between them. Two labels sharing a body share a
        method, and a break inside a moved arm became a return.</li>
        <li><span class="done">&#10003;</span> <strong>A VP8 macroblock
        predicted in sub-blocks no longer reads past its residuals.</strong>
        reconstructB overwrote its two loop counters with sample positions
        inside the loop that used them. After the first sub-block the
        residual was looked for at a key past the sixteen a macroblock
        holds, and every frame wrote warnings. The sample positions are
        named apart from the counters.</li>
        <li><span class="done">&#10003;</span> <strong>A VP8 macroblock
        predicted in sub-blocks no longer reads past its
        residuals.</strong> reconstructB overwrote its two loop counters
        with sample positions inside the loop that used them. After the
        first sub-block the residual was looked for at a key past the
        sixteen a macroblock holds, and every such frame wrote warnings.
        The sample positions are named apart from the counters.</li>
        <li><span class="done">&#10003;</span> <strong>A search asks for
        no more results than a reader would.</strong> The limit a request
        sends decides how many results are gathered. It also decides how
        many summaries are loaded after them. It was unbounded, so one
        request could ask for as much memory as it liked. It is held at a
        thousand now. A posting list claiming more items than its bytes can
        hold is refused.</li>
        <li><span class="done">&#10003;</span> <strong>The eighteen
        feed methods are given the values their bodies read.</strong>
        Splitting groupFeeds left each moved body naming locals the old
        method had worked out above the switch. Sixteen of the eighteen
        named a group model and none set it, so emptying the clipboard
        died on a call to a method on null. Each method works out what it
        reads now, and two small methods give the request's numbers and a
        post's title and description.</li>
        <li><span class="done">&#10003;</span> <strong>A cut post is
        taken away again after being copied.</strong> Before the split
        the cutpost label fell through into deletepost, and the split
        turned that into a break, so a cut copied and left the post
        standing. The switch falls through again. Pasting every clipped
        post also copied resources from an item that was never named,
        where it meant the item the loop is on.</li>
        <li><span class="done">&#10003;</span> <strong>The wiki activity
        is split into eleven methods.</strong> wiki ran to 930 lines,
        holding a switch of twelve labels and 318 lines of drawing after
        it. It is 189 lines now. The four long arms, the field cleaning,
        and the three parts of the drawing each became a method. The loop
        that made locals out of field names is gone, so a reader and a
        checker can both see what each part reads. What the method hands
        the view on twelve screens is unchanged, byte for byte.</li>
        <li><span class="done">&#10003;</span> <strong>A checkmark no
        longer stops a patch being cut.</strong> The cut refused a plan
        whose lines were rewritten, which is what setting a checkmark
        does. A removed line whose added counterpart differs only in its
        marker spans now counts as a marker change rather than a removal.
        A line that leaves the plan outright still stops the cut.</li>
        <li><span class="done">&#10003;</span> <strong>The wiki editor's
        activity is split into six methods.</strong> editWiki ran to 906
        lines. It is 198 now, with the file kept beside a page, the page's
        own settings, the values at the head of a page, the actions on the
        files beside it, and taking a file up, each a method. What the
        activity hands the view on seventeen screens is unchanged, byte
        for byte, the five new ones covering the settings screen, a page
        that is not there yet, a file opened for writing, a writing mark
        put back, and the files listing open.</li>
        <li><span class="done">&#10003;</span> <strong>The parts of the
        wiki and feed activities that stand on their own are checked by
        unit tests.</strong> Six cases read the request fields the two
        activities work from: a number that is absent, a zero, a title
        and a description cut to the length a post may hold, a page name
        written with a space, the two fields the wiki cannot do without,
        a group named by its name, and a page filled in from a template.
        Three methods were made public so a case can reach them. The
        whole suite runs 1,096 cases and 43,671 assertions.</li>
        <li><span class="done">&#10003;</span> <strong>Naming a group by
        its name clears the report that the group is absent.</strong> A
        request naming a group by name and not by number was refused for
        missing fields, since the report was cleared under the name of
        the field being read rather than under the name of the group. A
        unit test found this. It is cleared under group_id now, which is
        the name the report is written under.</li>
        <li><span class="done">&#10003;</span> <strong>Fifteen screens
        are driven in a browser and match what they drew before.</strong>
        Yioop was stood up and a headless Chrome signed in and walked the
        wiki and the feed: reading, the page list plain and filtered, the
        source, four editing screens, the history, the folder, the
        related pages, and the feed. Each was walked against the
        confirmed tree and against this one, and what the browser read
        off the pages is the same.</li>
        <li><span class="done">&#10003;</span> <strong>CodeTool gains a
        probe that says what an activity hands its view.</strong> A long
        method is split by moving parts of it out, and the question that
        answers is whether any screen it serves now shows something
        different. probe calls one activity once per screen named in a
        file and prints the whole of what it hands the view. The reader's
        mark, a hash and a count of seconds are written over, so two runs
        of an unchanged activity match to the byte.</li>
        <li><span class="done">&#10003;</span> <strong>The audit runs as
        one command.</strong> The eleven checks before a patch were run
        one at a time over eight scripts and the table was written by
        hand, so a table could say a check passed that was never run.
        devlog/audit.sh runs them over the files the patch carries and
        prints the table itself. It found a sandbox Config.php committed
        into this very patch.</li>
        <li><span class="done">&#10003;</span> <strong>A sandbox says
        what it is missing.</strong> devlog/check_env.sh names what a
        work clone needs and what is absent: PHP and its extensions, the
        word list the docblock check reads, a configured work directory,
        the two databases, and a browser to drive. The word list being
        absent had turned one real docblock fault into ninety-one, which
        is the kind of absence that hides rather than stops.</li>
        <li><span class="done">&#10003;</span> <strong>An old file is
        brought up one patch at a time.</strong> A patch touching a file
        with two hundred docblocks that say nothing cannot rewrite them
        all and do its own work. The cut now refuses only the first such
        file the patch touches and names the rest as waiting. The test
        file for the group and wiki component is this patch's, along with
        the tool file it touches.</li>
        <li><span class="done">&#10003;</span> <strong>A browser walks
        the same screens the probe reads.</strong> devlog/drive_screens.js
        signs in to a standing Yioop and fetches each screen a file names,
        reading what the browser puts on the page. Each screen is fetched
        in a context of its own, so a message one leaves cannot show on
        the next, which is the fault that made an earlier comparison
        disagree with itself. How long ago a row changed is written over,
        so two runs a minute apart match to the byte.</li>
        <li><span class="done">&#10003;</span> <strong>A log message
        that repeats is counted rather than written every time.</strong>
        Reading a broken index wrote tens of thousands of entries a
        second until the log was the slow part of the machine.
        crawlLogCounter stands in for crawlLog and writes one summary
        every so many seconds. It holds the count in memory and touches
        no file. The four posting decode guards call it, and their lines
        of figures are gone, since each figure would be counted as a
        message of its own.</li>
        </ol></li>
</ol>
</body>
</html>
X