Recherche avancée

Médias (0)

Mot : - Tags -/metadatas

Aucun média correspondant à vos critères n’est disponible sur le site.

Autres articles (86)

  • L’agrémenter visuellement

    10 avril 2011

    MediaSPIP est basé sur un système de thèmes et de squelettes. Les squelettes définissent le placement des informations dans la page, définissant un usage spécifique de la plateforme, et les thèmes l’habillage graphique général.
    Chacun peut proposer un nouveau thème graphique ou un squelette et le mettre à disposition de la communauté.

  • Websites made ​​with MediaSPIP

    2 mai 2011, par

    This page lists some websites based on MediaSPIP.

  • Creating farms of unique websites

    13 avril 2011, par

    MediaSPIP platforms can be installed as a farm, with a single "core" hosted on a dedicated server and used by multiple websites.
    This allows (among other things) : implementation costs to be shared between several different projects / individuals rapid deployment of multiple unique sites creation of groups of like-minded sites, making it possible to browse media in a more controlled and selective environment than the major "open" (...)

Sur d’autres sites (13474)

  • Linear Attribution Model : What Is It and How Does It Work ?

    16 février 2024, par Erin

    Want a more in-depth way to understand the effectiveness of your marketing campaigns ? Then, the linear attribution model could be the answer.

    Although you can choose from several different attribution models, a linear model is ideal for giving value to every touchpoint along the customer journey. It can help you identify your most effective marketing channels and optimise your campaigns. 

    So, without further ado, let’s explore what a linear attribution model is, when you should use it and how you can get started. 

    What is a linear attribution model ?

    A linear attribution model is a multi-touch method of marketing attribution where equal credit is given to each touchpoint. Every marketing channel used across the entire customer journey gets credit, and each is considered equally important. 

    So, if a potential customer has four interactions before converting, each channel gets 25% of the credit.

    The linear attribution model shares credit equally between each touchpoint

    Let’s look at how linear attribution works in practice using a hypothetical example of a marketing manager, Sally, who is looking for an alternative to Google Analytics. 

    Sally starts her conversion path by reading a Matomo article comparing Matomo to Google Analytics she finds when searching on Google. A few days later she signs up for a webinar she saw on Matomo’s LinkedIn page. Two weeks later, Sally gets a sign-off from her boss and decides to go ahead with Matomo. She visits the website and starts a free trial by clicking on one of the paid Google Ads. 

    Using a linear attribution model, we credit each of the channels Sally uses (organic traffic, organic social, and paid ads), ensuring no channel is overlooked in our marketing analysis. 

    Are there other types of attribution models ?

    Absolutely. There are several common types of attribution models marketing managers can use to measure the impact of channels in different ways. 

    Pros & Cons of Different Marketing Attribution Models
    • First interaction : Also called a first-touch attribution model, this method gives all the credit to the first channel in the customer journey. This model is great for optimising the top of your sales funnel.
    • Last interaction : Also called a last-touch attribution model, this approach gives all the credit to the last channel the customer interacts with. It’s a great model for optimising the bottom of your marketing funnel. 
    • Last non-direct interaction : This attribution model excludes direct traffic and credits the previous touchpoint. This is a fantastic alternative to a last-touch attribution model, especially if most customers visit your website before converting. 
    • Time decay attribution model : This model adjusts credit according to the order of the touchpoints. Those nearest the conversion get weighted the highest. 
    • Position-based attribution model : This model allocates 40% of the credit to the first and last touchpoints and splits the remaining 20% evenly between every other interaction.

    Why use a linear attribution model ?

    Marketing attribution is vital if you want to understand which parts of your marketing strategy are working. All of the attribution models described above can help you achieve this to some degree, but there are several reasons to choose a linear attribution model in particular. 

    It uses multi-touch attribution

    Unlike single-touch attribution models like first and last interaction, linear attribution is a multi-touch attribution model that considers every touchpoint. This is vital to get a complete picture of the modern customer journey, where customers interact with companies between 20 and 500 times

    Single-touch attribution models can be misleading by giving conversion credit to a single channel, especially if it was the customer’s last use. In our example above, Sally’s last interaction with our brand was through a paid ad, but it was hardly the most important. 

    It’s easy to understand

    Attribution models can be complicated, but linear attribution is easy to understand. Every touchpoint gets the same credit, allowing you to see how your entire marketing function works. This simplicity also makes it easy for marketers to take action. 

    It’s great for identifying effective marketing channels

    Because linear attribution is one of the few models that provides a complete view of the customer journey, it’s easy to identify your most common and influential touchpoints. 

    It accounts for the top and bottom of your funnel, so you can also categorise your marketing channels more effectively and make more informed decisions. For example, PPC ads may be a more common bottom-of-the-full touchpoint and should, therefore, not be used to target broad, top-of-funnel search terms.

    Are there any reasons not to use linear attribution ?

    Linear attribution isn’t perfect. Like all attribution models, it has its weaknesses. Specifically, linear attribution can be too simple, dilute conversion credit and unsuitable for long sales cycles.

    What are the reasons not to use linear attribution

    It can be too simple

    Linear attribution lacks nuance. It only considers touchpoints while ignoring other factors like brand image and your competitors. This is true for most attribution models, but it’s still important to point it out. 

    It can dilute conversion credit

    In reality, not every touchpoint impacts conversions to the same extent. In the example above, the social media post promoting the webinar may have been the most effective touchpoint, but we have no way of measuring this. 

    The risk with using a linear model is that credit can be underestimated and overestimated — especially if you have a long sales cycle. 

    It’s unsuitable for very long sales cycles

    Speaking of long sales cycles, linear attribution models won’t add much value if your customer journey contains dozens of different touchpoints. Credit will get diluted to the point where analysis becomes impossible, and the model will also struggle to measure the precise ways certain touchpoints impact conversions. 

    Should you use a linear attribution model ?

    A linear attribution model is a great choice for any company with shorter sales cycles or a reasonably straightforward customer journey that uses multiple marketing channels. In these cases, it helps you understand the contribution of each touchpoint and find your best channels. 

    It’s also a practical choice for small businesses and startups that don’t have a team of data scientists on staff or the budget to hire outside help. Because it’s so easy to set up and understand, anyone can start generating insights using this model. 

    How to set up a linear attribution model

    Are you sold on the idea of using a linear attribution model ? Then follow the steps below to get started :

    Set up marketing attribution in four steps

    Choose a marketing attribution tool

    Given the market is worth $3.1 billion, you won’t be surprised to learn there are plenty of tools to choose from. But choose carefully. The tool you pick can significantly impact your success with attribution modelling. 

    Take Google Analytics, for instance. While GA4 offers several marketing attribution models for free, including linear attribution, it lacks accuracy due to cookie consent rejection and data sampling. 

    Accurate marketing attribution is included as a feature in Matomo Cloud and is available as a plugin for Matomo On-Premise users. We support a full range of attribution models that use 100% accurate data because we don’t use data sampling, and cookie consent isn’t an issue (with the exception of Germany and the UK). That means you can trust our insights.

    Matomo’s marketing attribution is available out of the box, and we also provide access to raw data, allowing you to develop your custom attribution model. 

    Collect data

    The quality of your marketing attribution also depends on the quality and quantity of your data. It’s why you need to avoid a platform that uses data sampling. 

    This should include :

    • General data from your analytics platform, like pages visited and forms filled
    • Goals and conversions, which we’ll discuss in more detail in the next step
    • Campaign tracking data so you can monitor the behaviour of traffic from different referral channels
    • Behavioural data from features like Heatmaps or Session Recordings

    Set up goals and conversions

    You can’t assign conversion values to customer journey touchpoints if you don’t have conversion goals in place. That’s why the next step of the process is to set up conversion tracking in your web analytics platform. 

    Depending on your type of business and the product you sell, conversions could take one of the following forms :

    • A product purchase
    • Signing up for a webinar
    • Downloading an ebook
    • Filling in a form
    • Starting a free trial

    Setting up these kinds of goals is easy if you use Matomo. 

    Just head to the Goals section of the dashboard, click Manage Goals and then click the green Add A New Goal button. 

    Fill in the screen below, and add a Goal Revenue at the bottom of the page. Doing so will mean Matomo can automatically calculate the value of each touchpoint when using your attribution model. 

    A screenshot of Matomo's conversion dashboard

    If your analytics platform allows it, make sure you also set up Event Tracking, which will allow you to analyse how many users start to take a desired action (like filling in a form) but never complete the task. 

    Try Matomo for Free

    Get the web insights you need, without compromising data accuracy.

    No credit card required

    Test and validate

    As we’ve explained, linear attribution is a great model in some scenarios, but it can fall short if you have a long or complex sales funnel. Even if you’re sure it’s the right model for your company, testing and validating is important. 

    Ideally, your chosen attribution tool should make this process pretty straightforward. For example, Matomo’s Marketing Attribution feature makes comparing and contrasting three different attribution models easy. 

    Here we compare the performance of three attribution models—linear, first-touch, and last-non-direct—in Matomo’s Marketing Attribution dashboard, providing straightforward analysis.

    If you think linear attribution accurately reflects the value of your channels, you can start to analyse the insights it generates. If not, then consider using another attribution model.

    Don’t forget to take action from your marketing efforts, either. Linear attribution helps you spot the channels that contribute most to conversions, so allocate more resources to those channels and see if you can improve your conversion rate or boost your ROI. 

    Make the most of marketing attribution with Matomo

    A linear attribution model lets you measure every touchpoint in your customer journey. It’s an easy attribution model to start with and lets you identify and optimise your most effective marketing channels. 

    However, accurate data is essential if you want to benefit the most from marketing attribution data. If your web analytics solution doesn’t play nicely with cookies or uses sampled data, then your linear model isn’t going to tell you the whole story. 

    That’s why over 1 million sites trust Matomo’s privacy-focused web analytics, ensuring accurate data for a comprehensive understanding of customer journeys.

    Now you know what linear attribution modelling is, start employing the model today by signing up for a free 21-day trial, no credit card required. 

  • Understanding The Dreamcast GD-ROM Layout

    24 mars 2022, par Multimedia Mike — Sega Dreamcast

    I’m finally completing something I set out to comprehend over a decade ago. I wanted to understand how data is actually laid out on a Sega Dreamcast GD-ROM drive. I’m trying to remember why I even still care. There was something about how I wanted to make sure the contents of a set of Dreamcast demo discs was archived for study.


    Lot of 9 volumes of the Official Sega Dreamcast Magazine

    I eventually figured it out. Read on, if you are interested in the technical details. Or, if you would like to examine the fruits of this effort, check out the Dreamcast demo discs that I took apart and uploaded to the Internet Archive.

    If you care to read some geeky technical details of some of the artifacts on these sampler discs, check out this followup post on Dreamcast Finds.

    Motivation
    Why do I still care about this ? Well, see the original charter of this blog above. It’s mostly about studying multimedia formats, as well as the general operation of games and their non-multimedia data formats. It’s also something that has nagged at me ever since I extracted a bunch of Dreamcast discs years ago and tried to understand why the tracks were arranged the way they were, and how I could systematically split the files out of the filesystem. This turns out not to be as easy as it might sound, even if you can get past the obstacle of getting at the raw data.

    CD/CD-ROM Refresher
    As I laid out in my Grand Unified Theory of Compact Disc, every compact disc can be viewed conceptually as a string of sectors, where each sector is 2352 bytes long. The difference among the various CD types (audio CDs, various CD-ROM types) boils down to the format of contents of the 2352-byte sectors. For an audio CD, every sector’s 2352 bytes represents 1/75 of a second of CD-quality audio samples.

    Meanwhile, there are various sector layouts for different CD-ROM modes, useful for storing computer data. This post is most interested in “mode 1/form 1”, which uses 2048 of the 2352 bytes for data, while using the remaining bytes for error detection and correction codes. A filesystem (usually ISO-9660) is overlaid on these 2048-byte sectors in order to create data structures for organizing strings of sectors into files.

    A CD has between 1 and 99 tracks. A pure CD-ROM will have a single data track. Pure audio CDs tend to have numerous audio tracks, usually 1 per song. Mixed CDs are common. For software, this usually manifests as the first track being data and containing an ISO-9660 filesystem, followed by a series of audio tracks, sometimes for in-game music. For audio CDs, there is occasionally a data track at the end of the disc with some extra media types.

    GD-ROM Refresher
    The Dreamcast used optical discs called GD-ROMs, where the GD stands for “gigadisc”. These discs were designed to hold about 1 gigabyte of data, vs. the usual 650-700MB offered by standard CD solutions, while using the same laser unit as is used for CDs. I’m not sure how it achieved this exactly. I always assumed it was some sort of “double density” sector scheme. According to Wikipedia, the drive read the disc at a slower rate which allowed it to read more data (presumably the “pits” vs. “lands” which comprise the surface of an optical disc). This might be equivalent to my theory.

    The GD-ROM discs cannot be read in a standard optical drive. It is necessary to get custom software onto the Dreamcast which will ask the optical hardware to extract the sectors and exfiltrate them off of the unit somehow. There are numerous methods for this. Alternatively, just find rips that are increasingly plentiful around the internet. However, just because you might be able to find the data for a given disc does not mean that you can easily explore the contents.

    Typical Layout Patterns
    Going back to my study of the GD-ROM track layouts, 2 clear patterns emerge :

    All of the game data is packed into track 3 :


    GD-ROM Layout Type 1

    Track 3 has data, the last track has data, and the tracks in between contain standard CD audio :


    GD-ROM Layout Type 2

    Also, the disc is always, always 100% utilized.

    Track 1 always contains an ISO-9660 filesystem and can be read by any standard CD-ROM drive. And it usually has nothing interesting. Track 3 also contains what appears to be an ISO-9660 filesystem. However, if you have a rip of the track and try to mount the image with standard tools, it will not work. In the second layout, the data follows no obvious format.

    Cracking The Filesystem Code
    I figured out quite a few years ago that in the case of the consolidated data track 3, that’s simply a standard ISO-9660 filesystem that would work fine with standard ISO-9660 reading software… if the data track were located beginning at sector 45000. The filesystem data structures contain references to absolute sector numbers. Thus, if it were possible to modify some ISO-9660 software to assume the first sector is 45000, it ought to have no trouble interpreting the data.


    ISO-9660 In A Single Track

    How about the split data track format ? Actually, it works the same way. If all the data were sitting on its original disc, track 3 would have data structures pointing to strings of contiguous sectors (extents) in the final track, and those are the files.

    To express more succinctly : track 3 contains the filesystem root structure and the directory structures, while the final track contains the actual file data. How is the filesystem always 100% full ? Track 3 gets padded out with 0-sectors until the beginning of any audio sectors.


    ISO-9660 Spread Across 2 Tracks

    Why Lay Things Out Like This ?
    Why push the data as far out on the disc as possible ? A reasonable explanation for this would be for read performance. Compact discs operate on Constant Linear Velocity (CLV), vs. Constant Angular Velocity (CAV). The implication of this is that data on the outside of the disc is read faster than data on the inside. I once profiled this characteristic in order to prove it to myself, using both PC CD drives as well as a Dreamcast. By pushing the data to the outer sectors, graphical data gets loaded into RAM faster, and full motion videos, which require a certain minimum bitrate for a good experience, have a better guarantee that playback will be smooth.

    Implications For Repacking
    Once people figured out how to boot burned CDs in the Dreamcast, they had a new problem : Squeeze as much as 1 gigabyte down to around 650 megabytes at the most. It looks like the most straightforward strategy was to simply rework the filesystem to remove the often enormous amount of empty space in track 3.

    My understanding is that another major strategy is to re-encode certain large assets. Full motion video (FMV) assets are a good target here since the prevailing FMV middleware format used on Sega Dreamcast games was Sofdec, which is basically just MPEG-1 video. There is ample opportunity to transcode these files to lower bitrate settings to squeeze some bits (and a lot of visual quality) out of them.

    Further, if you don’t really care about the audio tracks, you could just replace them with brief spurts of silence.

    Making A Tool
    So I could make a tool that would process these collections of files representing a disc. I could also adapt it for various forms that a Dreamcast rip might take (I have found at least 3 so far). I could eventually expand it to handle lots of other disc formats (you know, something like Aaru does these days). And that would have been my modus operandi perhaps 10 or more years ago. And of course, the ambitious tool would have never seen daylight as I got distracted by other ideas.

    I wanted to get a solution up and running as quickly as possible this time. Here was my initial brainstorm : assemble all the tracks into a single, large disc while pretending the audio tracks consist of 2048-byte sectors. In doing so, I ought to be able to use fuseiso to mount the giant image, with a modification to look for the starting sector at a somewhat nonstandard location.

    To achieve the first part I wrote a quick Python script that processed the contents of a GDI file, which was stored alongside the ISO (data) and RAW (audio) track track rips from when I extracted the disc. The GDI is a very matter-of-fact listing of the tracks and their properties, e.g. :

    5
    1 0 4 2048 track01.iso 0
    2 721 0 2352 track02.raw 0
    3 45000 4 2048 track03.iso 0
    4 338449 0 2352 track04.raw 0
    5 349096 4 2048 track05.iso 0
    

    track number / starting sector / track type (4=data, 0=audio) / bytes per sector / filename / ??

    The script skips the first 2 filenames, instead writing 45000 zero sectors in order to simulate the CD-compatible area. Then, for each file, if it’s an ISO, append the data to the final data file ; if it’s audio, compute the number of sectors occupied, and then append that number of 2048-byte zero sectors to the final data file.

    Finally, to interpret the filesystem, I used an old tool that I’ve relied upon for a long time– fuseiso. This is a program that leverages Filesystem in Userspace (FUSE) to mount ISO-9660 filesystems as part of the local filesystem, without needing root privileges. The original source hasn’t been updated for 15 years, but I found a repo that attempts to modernize it slightly. I forked a version which fixes a few build issues.

    Anyway, I just had to update a table to ask it to start looking for the root ISO-9660 filesystem at a different location than normal. Suddenly, after so many years, I was able to freely browse a GD-ROM filesystem directly under Linux !

    Conclusion And Next Steps
    I had to hack the fuseiso3 tool a bit in order to make this work. I don’t think it’s especially valuable to make sure anyone can run with the same modifications since the tool assumes that a GD-ROM rip has been processed through the exact pipeline I described above.

    I have uploaded all of the North American Dreamcast demo discs to archive.org. See this post for a more granular breakdown of what this entails. In the course of this exercise, I also found some European demo discs that could use the same extraction.

    What else ? Should I perform the same extraction experiment for all known Dreamcast games ? Would anyone care ? Maybe if there’s a demand for it.

    Here is a followup on the interesting and weird things I have found on these discs so far.

    The post Understanding The Dreamcast GD-ROM Layout first appeared on Breaking Eggs And Making Omelettes.

  • FFmpeg decoded video is garbled mess with no audio. How can I fix it ? [closed]

    14 septembre 2023, par Señor Tonto

    I am trying to use FFmpeg 6.0 (which seems to have a terrible lack of documentation) to decode a .mp4/.mov file & play its audio. I am testing this on a .mov file (I tried also with a .mp4, same result) of an old animation I found. This turns out as a garbled mess that looks like an old film movie with no audio. Now, I used some old tutorials & managed to with much trial get the code I have now, which is of course half-working. I am using SDL to output the video. I'm not sure if it's a problem on the SDL end or the FFmpeg end at the moment (but I'm sure the video is a mix of the two & the audio because of SDL) I would appreciate some help as there's very little documentation I can find that isn't outdated & the deprecated list on FFmpeg hardly helps when I need to fix API-related changes. Here's my code

    


    LRESULT CALLBACK WindowProcessMessages(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {&#xA;    OPENFILENAME ofn;&#xA;&#xA;    wchar_t g_filePath[MAX_PATH] = L"";&#xA;    char szFile[MAX_PATH] = "";&#xA;    wchar_t wFile[MAX_PATH] = L"";&#xA;    char data[MAX_PATH];&#xA;    DWORD bytesRead = 0;&#xA;    BOOL bSuccess = FALSE;&#xA;    static int iVscrollPos, cyClient, cyChar, cxChar, cxCaps, iMaxVerticalScroll = 0, iNumLines = 0;&#xA;    HDC hdc;&#xA;    TEXTMETRIC tm;&#xA;    static int tabIndex;&#xA;    std::wstring filePath;&#xA;&#xA;    //FFmpeg variables&#xA;    static AVFormatContext* format_ctx = nullptr; &#xA;    static AVCodecContext* video_codec_ctx = nullptr;&#xA;    static AVCodecContext* audio_codec_ctx = nullptr;&#xA;    static AVFrame* video_frame = nullptr;&#xA;    static AVFrame* audio_frame = nullptr;&#xA;    static AVPacket packet;&#xA;    static struct SwsContext* sws_ctx = nullptr;&#xA;    static int video_stream_idx = -1;&#xA;    static int audio_stream_idx = -1;&#xA;    static int video_width = 0;&#xA;    static int video_height = 0;&#xA;    static HWND hVideoWnd = nullptr;&#xA;    static HDC hVideoDC = nullptr;&#xA;    static HBITMAP hVideoBitmap = nullptr;&#xA;&#xA;    switch (msg) {&#xA;    case WM_CREATE:&#xA;        break;&#xA;    case WM_SIZE:&#xA;        break;&#xA;    case WM_NOTIFY:&#xA;        //code for handling tab switching &amp; creating all content inside the tabs, currently half-working, perhaps give each tab a show &amp; update win call?&#xA;        if (((LPNMHDR)lParam)->code == TCN_SELCHANGE) {&#xA;            tabIndex = TabCtrl_GetCurSel(g_hWndTabs);&#xA;            if (tabIndex == 0) {&#xA;                DestroyWindow(openFileBtn);&#xA;                DestroyWindow(saveFileBtn);&#xA;                DestroyWindow(emboldenBtn);&#xA;                DestroyWindow(italiciseBtn);&#xA;                DestroyWindow(hEditControl);&#xA;                DestroyWindow(hScrollContainer);&#xA;&#xA;                //this code reloads table data &amp; reconstructs the table whenever the &#x27;table view&#x27; tab is opened,&#xA;                std::wifstream infile("tabledata.txt");&#xA;                while (numRows > 0) {&#xA;                    numRows--;&#xA;                }&#xA;                while (true) {&#xA;                    // Read in data for a row&#xA;                    wchar_t buf1[MAX_PATH], buf2[MAX_PATH], buf3[MAX_PATH];&#xA;                    if (!(infile >> buf1 >> buf2 >> buf3)) {&#xA;                        // End of file reached, break out of loop&#xA;                        break;&#xA;                    }&#xA;&#xA;                    // Create new row window for this row of data&#xA;                    HWND hRow = CreateWindowEx(0, L"STATIC", nullptr,&#xA;                        WS_CHILD | WS_VISIBLE | WS_BORDER,&#xA;                        startX, startY &#x2B; numRows * ROW_HEIGHT,&#xA;                        3 * CELL_WIDTH, ROW_HEIGHT,&#xA;                        g_hWndMain, nullptr, g_hInstance, nullptr);&#xA;&#xA;                    // Create cell edit controls within the new row&#xA;                    HWND hCell1 = CreateWindowEx(0, L"EDIT", nullptr,&#xA;                        WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | WS_BORDER,&#xA;                        0, 0, CELL_WIDTH, ROW_HEIGHT,&#xA;                        hRow, nullptr, g_hInstance, nullptr);&#xA;                    HWND hCell2 = CreateWindowEx(0, L"EDIT", nullptr,&#xA;                        WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | WS_BORDER,&#xA;                        CELL_WIDTH, 0, CELL_WIDTH, ROW_HEIGHT,&#xA;                        hRow, nullptr, g_hInstance, nullptr);&#xA;                    HWND hCell3 = CreateWindowEx(0, L"EDIT", nullptr,&#xA;                        WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | WS_BORDER,&#xA;                        2 * CELL_WIDTH, 0, CELL_WIDTH, ROW_HEIGHT,&#xA;                        hRow, nullptr, g_hInstance, nullptr);&#xA;&#xA;                    // Set the text of each cell edit control&#xA;                    SetWindowTextW(hCell1, buf1);&#xA;                    SetWindowTextW(hCell2, buf2);&#xA;                    SetWindowTextW(hCell3, buf3);&#xA;&#xA;                    // Update the row window&#xA;                    InvalidateRect(hRow, nullptr, TRUE);&#xA;                    UpdateWindow(hRow);&#xA;&#xA;                    numRows&#x2B;&#x2B;;&#xA;                }&#xA;&#xA;&#xA;&#xA;                tblHeaderOne = CreateWindow(L"STATIC", L"Header 1",&#xA;                    WS_CHILD | WS_VISIBLE | SS_CENTER | WS_BORDER,&#xA;                    0, 40, 110, 20,&#xA;                    g_hWndMain, (HMENU)CELL_1_ID, g_hInstance, NULL);&#xA;                InvalidateRect(tblHeaderOne, NULL, TRUE);&#xA;                UpdateWindow(tblHeaderOne);&#xA;&#xA;&#xA;                tblHeaderTwo = CreateWindow(L"STATIC", L"Header 2",&#xA;                    WS_CHILD | WS_VISIBLE | SS_CENTER | WS_BORDER,&#xA;                    110, 40, 110, 20,&#xA;                    g_hWndMain, (HMENU)CELL_2_ID, g_hInstance, NULL);&#xA;                InvalidateRect(tblHeaderTwo, NULL, TRUE);&#xA;                UpdateWindow(tblHeaderTwo);&#xA;&#xA;                tblHeaderThree = CreateWindow(L"STATIC", L"Header 3",&#xA;                    WS_CHILD | WS_VISIBLE | SS_CENTER | WS_BORDER,&#xA;                    220, 40, 110, 20,&#xA;                    g_hWndMain, (HMENU)CELL_3_ID, g_hInstance, NULL);&#xA;                InvalidateRect(tblHeaderThree, NULL, TRUE);&#xA;                UpdateWindow(tblHeaderThree);&#xA;&#xA;                // Create button to add new row&#xA;                addRowBtn = CreateWindow(L"BUTTON", L"Add Row",&#xA;                    WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,&#xA;                    390, 40, 100, 30,&#xA;                    g_hWndMain, (HMENU)ADD_ROW_BTN, g_hInstance, NULL);&#xA;                InvalidateRect(addRowBtn, NULL, TRUE);&#xA;                UpdateWindow(addRowBtn);&#xA;&#xA;            }&#xA;            else if (tabIndex == 1) {&#xA;&#xA;                DestroyWindow(hRow);&#xA;                DestroyWindow(tableContainer);&#xA;                DestroyWindow(tblHeaderOne);&#xA;                DestroyWindow(tblHeaderTwo);&#xA;                DestroyWindow(tblHeaderThree);&#xA;                DestroyWindow(addRowBtn);&#xA;&#xA;&#xA;&#xA;                openFileBtn = CreateWindow(L"BUTTON", L"Open File",&#xA;                    WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,&#xA;                    10, 40, 150, 25,&#xA;                    g_hWndMain, (HMENU)OPEN_FILE_BTN, NULL, NULL);&#xA;                InvalidateRect(openFileBtn, NULL, TRUE);&#xA;                UpdateWindow(openFileBtn);&#xA;&#xA;                saveFileBtn = CreateWindow(L"BUTTON", L"Save File",&#xA;                    WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,&#xA;                    10, 80, 150, 25,&#xA;                    g_hWndMain, (HMENU)SAVE_FILE_BTN, nullptr, nullptr);&#xA;                InvalidateRect(saveFileBtn, NULL, TRUE);&#xA;                UpdateWindow(saveFileBtn);&#xA;&#xA;                hScrollContainer = CreateWindowEx(WS_EX_CLIENTEDGE, L"SCROLLBAR", NULL,&#xA;                    WS_CHILD | WS_VISIBLE | WS_VSCROLL | SBS_VERT,&#xA;                    170, 40, 600, 300,&#xA;                    g_hWndMain, (HMENU)SCROLL_CONTAINER, g_hInstance, NULL);&#xA;&#xA;&#xA;                hEditControl = CreateWindowEx(WS_EX_CLIENTEDGE, L"EDIT", NULL,&#xA;                    WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,&#xA;                    0, 0, 600, 300,&#xA;                    hScrollContainer, (HMENU)EDIT_CONTROL, g_hInstance, NULL);&#xA;                InvalidateRect(hEditControl, NULL, TRUE);&#xA;                UpdateWindow(hEditControl);&#xA;&#xA;                SetFocus(hEditControl);&#xA;&#xA;                SetScrollRange(hScrollContainer, SB_VERT, 0, 5, TRUE);&#xA;                SetScrollPos(hScrollContainer, SB_VERT, 0, TRUE);&#xA;&#xA;                // Add embolden button&#xA;                emboldenBtn = CreateWindow(L"BUTTON", L"Embolden",&#xA;                    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,&#xA;                    10, 120, 100, 25,&#xA;                    g_hWndMain, (HMENU)EMBOLDEN_BTN, nullptr, nullptr);&#xA;                InvalidateRect(emboldenBtn, NULL, TRUE);&#xA;                UpdateWindow(emboldenBtn);&#xA;&#xA;                // Add italicise button&#xA;                italiciseBtn = CreateWindow(L"BUTTON", L"Italicise",&#xA;                    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,&#xA;                    10, 150, 100, 25,&#xA;                    g_hWndMain, (HMENU)ITALICISE_BTN, nullptr, nullptr);&#xA;                InvalidateRect(italiciseBtn, NULL, TRUE);&#xA;                UpdateWindow(italiciseBtn);&#xA;            }&#xA;            else if (tabIndex == 2) {&#xA;                DestroyWindow(hRow);&#xA;                DestroyWindow(tableContainer);&#xA;                DestroyWindow(tblHeaderOne);&#xA;                DestroyWindow(tblHeaderTwo);&#xA;                DestroyWindow(tblHeaderThree);&#xA;                DestroyWindow(addRowBtn);&#xA;                DestroyWindow(openFileBtn);&#xA;                DestroyWindow(saveFileBtn);&#xA;                DestroyWindow(emboldenBtn);&#xA;                DestroyWindow(italiciseBtn);&#xA;                DestroyWindow(hEditControl);&#xA;                DestroyWindow(hScrollContainer);&#xA;&#xA;                // Create container for player&#xA;                hWMPContainer = CreateWindowEx(WS_EX_CLIENTEDGE, L"STATIC", nullptr,&#xA;                    WS_CHILD | WS_VISIBLE | SS_CENTER | SS_GRAYFRAME,&#xA;                    50, 50, 700, 400,&#xA;                    g_hWndMain, nullptr, g_hInstance, nullptr);&#xA;&#xA;                // Create Open .mp4/.mov button&#xA;                OpenMp4Btn = CreateWindow(L"BUTTON", L"Open .mp4/.mov",&#xA;                    WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,&#xA;                    50, 460, 150, 30,&#xA;                    g_hWndMain, (HMENU)FILEMENU_OPEN_FILE_BTN, nullptr, nullptr);&#xA;                InvalidateRect(OpenMp4Btn, nullptr, TRUE);&#xA;                UpdateWindow(OpenMp4Btn);&#xA;&#xA;&#xA;                // Create play/pause button&#xA;                hPlayBtn = CreateWindow(L"BUTTON", L"Play",&#xA;                    WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,&#xA;                    350, 460, 100, 30,&#xA;                    g_hWndMain, nullptr, g_hInstance, nullptr);&#xA;                InvalidateRect(hPlayBtn, nullptr, TRUE);&#xA;                UpdateWindow(hPlayBtn);&#xA;&#xA;            }&#xA;        }&#xA;        return DefWindowProc(hwnd, msg, wParam, lParam);&#xA;        break;&#xA;    case WM_MOUSEWHEEL:&#xA;        //also WIP, will be developed after WM_VSCROLL is established&#xA;        break;&#xA;    case WM_VSCROLL:&#xA;        //to be developed 9516555&#xA;    case WM_PAINT:&#xA;        break;&#xA;    case WM_COMMAND:&#xA;        //all commands passed to the window through buttons, menus, forms, etc.&#xA;        if (HIWORD(wParam) == BN_CLICKED) {&#xA;            if ((HWND)lParam == addRowBtn) {&#xA;                const wchar_t* ROW_CLASS_NAME = L"Row Window";&#xA;                WNDCLASS rowWc{};&#xA;                rowWc.hInstance = g_hInstance;&#xA;                rowWc.lpszClassName = ROW_CLASS_NAME;&#xA;                rowWc.hCursor = LoadCursor(nullptr, IDC_ARROW);&#xA;                rowWc.hbrBackground = (HBRUSH)COLOR_WINDOW;&#xA;                rowWc.lpfnWndProc = AddRowDlgProc;&#xA;                RegisterClass(&amp;rowWc);&#xA;&#xA;                hWnd = CreateWindow(ROW_CLASS_NAME, L"Row Window",&#xA;                    WS_OVERLAPPEDWINDOW,&#xA;                    CW_USEDEFAULT, CW_USEDEFAULT,&#xA;                    250, 200,&#xA;                    nullptr, nullptr, nullptr, nullptr);&#xA;&#xA;                // Create text box controls&#xA;                hWndCell1Label = CreateWindowW(&#xA;                    L"STATIC", L"Cell 1:",&#xA;                    WS_CHILD | WS_VISIBLE | SS_LEFT,&#xA;                    10, 10, 50, 20,&#xA;                    hWnd, NULL, g_hInstance, NULL);&#xA;&#xA;&#xA;                hWndCell1Edit = CreateWindowW(&#xA;                    L"EDIT", NULL,&#xA;                    WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL,&#xA;                    65, 10, 100, 20,&#xA;                    hWnd, NULL, g_hInstance, NULL);&#xA;&#xA;&#xA;                hWndCell2Label = CreateWindowW(&#xA;                    L"STATIC", L"Cell 2:",&#xA;                    WS_CHILD | WS_VISIBLE | SS_LEFT,&#xA;                    10, 40, 50, 20,&#xA;                    hWnd, NULL, g_hInstance, NULL);&#xA;&#xA;&#xA;                hWndCell2Edit = CreateWindowW(&#xA;                    L"EDIT", NULL,&#xA;                    WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL,&#xA;                    65, 40, 100, 20,&#xA;                    hWnd, NULL, g_hInstance, NULL);&#xA;&#xA;                hWndCell3Label = CreateWindowW(&#xA;                    L"STATIC", L"Cell 3:",&#xA;                    WS_CHILD | WS_VISIBLE | SS_LEFT,&#xA;                    10, 70, 50, 20,&#xA;                    hWnd, NULL, g_hInstance, NULL);&#xA;&#xA;&#xA;                hWndCell3Edit = CreateWindowW(&#xA;                    L"EDIT", NULL,&#xA;                    WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL,&#xA;                    65, 70, 100, 20,&#xA;                    hWnd, NULL, g_hInstance, NULL);&#xA;&#xA;&#xA;                // Create OK and Cancel button controls&#xA;                hWndOkButton = CreateWindowW(&#xA;                    L"BUTTON", L"OK",&#xA;                    WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,&#xA;                    40, 110, 50, 25,&#xA;                    hWnd, (HMENU)IDOK, g_hInstance, NULL);&#xA;&#xA;&#xA;                hWndCancelButton = CreateWindowW(&#xA;                    L"BUTTON", L"Cancel",&#xA;                    WS_CHILD | WS_VISIBLE,&#xA;                    100, 110, 50, 25,&#xA;                    hWnd, (HMENU)IDCANCEL, g_hInstance, NULL);&#xA;&#xA;&#xA;                ShowWindow(hWnd, SW_SHOW);&#xA;                UpdateWindow(hWnd);&#xA;&#xA;                MSG msg{};&#xA;                while (GetMessage(&amp;msg, nullptr, 0, 0)) {&#xA;                    TranslateMessage(&amp;msg);&#xA;                    DispatchMessage(&amp;msg);&#xA;                }&#xA;&#xA;            }&#xA;            if ((HWND)lParam == openFileBtn) {&#xA;                // Open File button clicked, show open file dialoge and load the selected file into the edit control&#xA;                OPENFILENAME ofn = { };&#xA;                WCHAR szFile[MAX_PATH] = L"";&#xA;                ofn.lStructSize = sizeof(OPENFILENAME);&#xA;                ofn.hwndOwner = hWnd;&#xA;                ofn.lpstrFilter = L"Text files\0*.txt\0All files\0*.*\0";&#xA;                ofn.lpstrFile = szFile;&#xA;                ofn.nMaxFile = MAX_PATH;&#xA;                ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;&#xA;                if (GetOpenFileName(&amp;ofn)) {&#xA;                    // Read file and set text of edit box&#xA;                    std::ifstream infile(ofn.lpstrFile, std::ios::in | std::ios::binary);&#xA;                    if (infile) {&#xA;                        std::wstring text((std::istreambuf_iterator<char>(infile)), std::istreambuf_iterator<char>());&#xA;                        SetWindowText(hEditControl, text.c_str());&#xA;                        filePath = ofn.lpstrFile;&#xA;                    }&#xA;                }&#xA;            }&#xA;            if ((HWND)lParam == saveFileBtn) {&#xA;                if (!filePath.empty()) {&#xA;                    std::wofstream outfile(filePath, std::ios::out | std::ios::binary); // use std::wofstream instead of std::ofstream&#xA;                    if (outfile) {&#xA;                        int textLength = GetWindowTextLength(hEditControl);&#xA;                        if (textLength != 0) {&#xA;                            wchar_t* textBuffer = new wchar_t[textLength &#x2B; 1];&#xA;                            GetWindowTextW(hEditControl, textBuffer, textLength &#x2B; 1);&#xA;                            outfile.write(textBuffer, textLength * sizeof(wchar_t));&#xA;                            delete[] textBuffer;&#xA;                        }&#xA;                    }&#xA;                    outfile.close();&#xA;                    MessageBox(NULL, L"File saved successfully!", L"File save", MB_OK);&#xA;                }&#xA;&#xA;            }&#xA;            //The following are not currently working due to an issue in connecting to the edit box for char type manipulation&#xA;            if ((HWND)lParam == emboldenBtn) {&#xA;&#xA;            }&#xA;            if ((HWND)lParam == italiciseBtn) {&#xA;&#xA;            }&#xA;            //cannot or failed to open video file &#xA;            if ((HWND)lParam == OpenMp4Btn) {&#xA;                OPENFILENAMEA ofn = {};&#xA;                ofn.lStructSize = sizeof(ofn);&#xA;                ofn.hwndOwner = hwnd;&#xA;                ofn.lpstrFilter = "Video Files (*.mp4;*.mov)\0*.mp4;*.mov\0All Files (*.*)\0*.*\0";&#xA;                char szFile[MAX_PATH] = {};&#xA;                ofn.lpstrFile = szFile;&#xA;                ofn.nMaxFile = MAX_PATH;&#xA;                ofn.lpstrTitle = "Open Video File";&#xA;                ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;&#xA;                if (GetOpenFileNameA(&amp;ofn)) {&#xA;                    // Initialize SDL&#xA;                    SDL_Init(SDL_INIT_VIDEO);&#xA;&#xA;                    // Open the selected video file&#xA;                    AVFormatContext* format_ctx = nullptr;&#xA;                    if (avformat_open_input(&amp;format_ctx, szFile, nullptr, nullptr) != 0) {&#xA;                        // Error handling&#xA;                    }&#xA;&#xA;                    // Retrieve stream information&#xA;                    if (avformat_find_stream_info(format_ctx, nullptr) &lt; 0) {&#xA;                        // Error handling&#xA;                    }&#xA;&#xA;                    // Find the video and audio streams&#xA;                    int video_stream_idx = av_find_best_stream(format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);&#xA;                    int audio_stream_idx = av_find_best_stream(format_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);&#xA;&#xA;                    // Open the video and audio codecs&#xA;                    AVCodecContext* video_codec_ctx = avcodec_alloc_context3(nullptr);&#xA;                    avcodec_parameters_to_context(video_codec_ctx, format_ctx->streams[video_stream_idx]->codecpar);&#xA;                    AVCodec* video_codec = const_cast(avcodec_find_decoder(video_codec_ctx->codec_id));&#xA;                    if (avcodec_open2(video_codec_ctx, video_codec, nullptr) &lt; 0) {&#xA;                        // Error handling&#xA;                    }&#xA;&#xA;                    AVCodecContext* audio_codec_ctx = avcodec_alloc_context3(nullptr);&#xA;                    avcodec_parameters_to_context(audio_codec_ctx, format_ctx->streams[audio_stream_idx]->codecpar);&#xA;                    AVCodec* audio_codec = const_cast(avcodec_find_decoder(audio_codec_ctx->codec_id));&#xA;                    if (avcodec_open2(audio_codec_ctx, audio_codec, nullptr) &lt; 0) {&#xA;                        // Error handling&#xA;                    }&#xA;                    // Allocate memory for the video and audio frames&#xA;                    AVFrame* video_frame = av_frame_alloc();&#xA;                    AVFrame* audio_frame = av_frame_alloc();&#xA;&#xA;                    // Create a window for displaying the video frames&#xA;                    SDL_Window* window = SDL_CreateWindow("Video Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, video_codec_ctx->width, video_codec_ctx->height, SDL_WINDOW_SHOWN);&#xA;&#xA;                    // Create a renderer for displaying the video frames&#xA;                    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);&#xA;&#xA;                    // Create a texture for displaying the video frames&#xA;                    SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_BGR24, SDL_TEXTUREACCESS_STREAMING, video_codec_ctx->width, video_codec_ctx->height);&#xA;&#xA;                    // Create a scaling context for converting the video frames to RGB&#xA;                    SwsContext* sws_ctx = sws_getContext(video_codec_ctx->width, video_codec_ctx->height, video_codec_ctx->pix_fmt,&#xA;                        video_codec_ctx->width, video_codec_ctx->height, AV_PIX_FMT_BGR24,&#xA;                        SWS_BILINEAR, nullptr, nullptr, nullptr);&#xA;&#xA;                    // Decode and display the video frames&#xA;                    AVPacket packet;&#xA;                    while (av_read_frame(format_ctx, &amp;packet) >= 0) {&#xA;                        if (packet.stream_index == video_stream_idx) {&#xA;                            avcodec_send_packet(video_codec_ctx, &amp;packet);&#xA;                            while (avcodec_receive_frame(video_codec_ctx, video_frame) == 0) {&#xA;                                sws_scale(sws_ctx, video_frame->data, video_frame->linesize, 0, video_codec_ctx->height,&#xA;                                    video_frame->data, video_frame->linesize);&#xA;                                SDL_UpdateTexture(texture, NULL, video_frame->data[0], video_frame->linesize[0]);&#xA;                                SDL_RenderClear(renderer);&#xA;                                SDL_RenderCopy(renderer, texture, NULL, NULL);&#xA;                                SDL_RenderPresent(renderer);&#xA;                            }&#xA;                        }&#xA;                        av_packet_unref(&amp;packet);&#xA;                    }&#xA;&#xA;                    // Free the allocated memory and close the codecs&#xA;                    av_frame_free(&amp;video_frame);&#xA;                    av_frame_free(&amp;audio_frame);&#xA;                    avcodec_free_context(&amp;video_codec_ctx);&#xA;                    avcodec_free_context(&amp;audio_codec_ctx);&#xA;                    avformat_close_input(&amp;format_ctx);&#xA;&#xA;                    // Destroy the window, renderer, and texture&#xA;                    SDL_DestroyTexture(texture);&#xA;                    SDL_DestroyRenderer(renderer);&#xA;                    SDL_DestroyWindow(window);&#xA;&#xA;                    // Quit SDL&#xA;                    SDL_Quit();&#xA;                }&#xA;            }&#xA;        }&#xA;        break;&#xA;    case WM_DESTROY:&#xA;        //code that executes on window destruction&#xA;        PostQuitMessage(0);&#xA;        return 0;&#xA;    default:&#xA;        return DefWindowProc(hwnd, msg, wParam, lParam);&#xA;    }&#xA;}&#xA;</char></char>

    &#xA;

    I am doing this on MVSC2022 in a C++ win32 application if it's relevant, I will attach some images of the video : some of the videosome more of the videoA screenshot of the output screen

    &#xA;