
Recherche avancée
Autres articles (18)
-
Supporting all media types
13 avril 2011, parUnlike most software and media-sharing platforms, MediaSPIP aims to manage as many different media types as possible. The following are just a few examples from an ever-expanding list of supported formats : images : png, gif, jpg, bmp and more audio : MP3, Ogg, Wav and more video : AVI, MP4, OGV, mpg, mov, wmv and more text, code and other data : OpenOffice, Microsoft Office (Word, PowerPoint, Excel), web (html, CSS), LaTeX, Google Earth and (...)
-
Gestion générale des documents
13 mai 2011, parMédiaSPIP ne modifie jamais le document original mis en ligne.
Pour chaque document mis en ligne il effectue deux opérations successives : la création d’une version supplémentaire qui peut être facilement consultée en ligne tout en laissant l’original téléchargeable dans le cas où le document original ne peut être lu dans un navigateur Internet ; la récupération des métadonnées du document original pour illustrer textuellement le fichier ;
Les tableaux ci-dessous expliquent ce que peut faire MédiaSPIP (...) -
Les vidéos
21 avril 2011, parComme les documents de type "audio", Mediaspip affiche dans la mesure du possible les vidéos grâce à la balise html5 .
Un des inconvénients de cette balise est qu’elle n’est pas reconnue correctement par certains navigateurs (Internet Explorer pour ne pas le nommer) et que chaque navigateur ne gère en natif que certains formats de vidéos.
Son avantage principal quant à lui est de bénéficier de la prise en charge native de vidéos dans les navigateur et donc de se passer de l’utilisation de Flash et (...)
Sur d’autres sites (3842)
-
Understanding The Dreamcast GD-ROM Layout
24 mars 2022, par Multimedia Mike — Sega DreamcastI’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.
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 :
Track 3 has data, the last track has data, and the tracks in between contain standard CD audio :
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.
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.
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.
-
node.js ffmpeg spawn child_process unexpected data output and not possible to kill or stop
1er septembre 2021, par PLNR
I'm rather new to backend stuff, so please excuse, if my question is trivial.

For an Intranet project, I want to present a video element in a webpage (React, HLS.js Player) with the possibility reload the stream in case it hung.

The video sources are mpeg-ts streams delivered as udp multicast.

A small node.js / express server should handle the ffmpeg commands, to transcode the multicast to hls to display them in a browser.



My Problem is, that I am unable to stop the process...

I've tried to SIGINT, SIGTERM the process with it's pid, but the encoding is just going on.

Here is the code I wrote for transcoding so far :


const express = require("express");
const { spawn, exec } = require("child_process");
const cors = require("cors");
const process = require("process")

let ls;
let pid;

const app = express();

app.use(cors());

app.get('/cam/:source', (body) => {
 const cam = body.params.source;
 console.log(cam);

 let source = "udp://239.1.1.1:4444";
 let stream = "/var/www/html/streams/tmp/cam1.m3u8"


 stream = spawn("ffmpeg", ["-re", "-i", source, "-c:v", "libx264", "-crf", "21", "-preset", "veryfast", "-c:a", "aac", "-b:a", "128k", "-ac", "2", "-f", "hls", "-hls_list_size", "5", "-hls_flags", "delete_segments", stream], {detached: true});

 pid = stream.pid;

 stream.stdout.on("data", data => {
 console.log(`stdout: ${data}`);
 });

 stream.stderr.on("data", data => {
 console.log(`stderr: ${data}`);
 console.log("pid: ", pid);
 });

 stream.on("error", error => {
 console.log(`error: ${error.message}`);
 });

 stream.on("close", code => {
 console.log(`child process exited with code ${code}`);
 });
})

app.get('/cam/quit', () => {
 process.kill(pid, 'SIGINT')
})

app.listen(5000, ()=> {
 console.log('Listening');
})



On requesting the /cam/quit i would expect the process to stop... but it doesn't.

I've tried :

app.get('/cam/quit', () => {
 process.kill(pid, 'SIGINT')
})



app.get('/cam/quit', () => {
 process.kill(stream.pid, 'SIGINT')
})



app.get('/cam/quit', () => {
 stream.kill(pid, 'SIGINT')
})



app.get('/cam/quit', () => {
 stream.kill(0, 'SIGINT')
})



But none of this worked... do I miss something ? Is there something I need to change ?


Problem 2 - Output :

The output is emitted on stderr... even the process is working as expected.

This is maybe only cosmetics, but it makes me wondering.

Here is the terminal output :

[nodemon] starting `node server.js`
Listening
camera stream reloaded
stderr: ffmpeg version 4.3.2-0+deb11u1ubuntu1 Copyright (c) 2000-2021 the FFmpeg developers
 built with gcc 10 (Ubuntu 10.2.1-20ubuntu1)
 configuration: --prefix=/usr --extra-version=0+deb11u1ubuntu1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libdav1d --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librabbitmq --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-pocketsphinx --enable-libmfx --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
 libavutil 56. 51.100 / 56. 51.100
 libavcodec 58. 91.100 / 58. 91.100
 libavformat 58. 45.100 / 58. 45.100
 libavdevice 58. 10.100 / 58. 10.100
 libavfilter 7. 85.100 / 7. 85.100
 libavresample 4. 0. 0 / 4. 0. 0
 libswscale 5. 7.100 / 5. 7.100
 libswresample 3. 7.100 / 3. 7.100
 libpostproc 55. 7.100 / 55. 7.100

pid: 4206
stderr: Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'udp://239.1.1.1':
 Metadata:
 major_brand : isom
 minor_version : 512
 compatible_brands: isomiso2avc1mp41
 creation_time : 
pid: 4206
stderr: 1970-01-01T00:00:00.000000Z
 encoder : Lavf52.54.0
 Duration: 01:55:59.20, start: 0.000000, bitrate: 1436 kb/s
 Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p, 720x384 [SAR 1:1 DAR 15:8], 1272 kb/s, 25 fps, 25 tbr, 25 tbn, 50 tbc (default)
 Metadata:
 creation_time : 1970-01-01T00:00:00.000000Z
 handler_name : VideoHandler
 Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 159 kb/s (default)
 Metadata:
 creation_time : 1970-01-01T00:00:00.000000Z
 handler_name : SoundHandler

pid: 4206
stderr: Stream mapping:
 Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264))
 Stream #0:1 -> #0:1 (aac (native) -> aac (native))
Press [q] to stop, [?] for help

pid: 4206
stderr: [libx264 @ 0x5616b38b5440] using SAR=1/1

pid: 4206
stderr: [libx264 @ 0x5616b38b5440] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2

pid: 4206
stderr: [libx264 @ 0x5616b38b5440] profile High, level 3.0, 4:2:0, 8-bit

pid: 4206
stderr: [libx264 @ 0x5616b38b5440] 264 - core 160 r3011 cde9a93 - H.264/MPEG-4 AVC codec - Copyleft 2003-2020 - http://www.videolan.org/x264.html - options: cabac=1 ref=1 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=2 psy=1 psy_rd=1.00:0.00 mixed_ref=0 me_range=16 chroma_me=1 trellis=0 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=0 threads=6 lookahead_threads=2 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=1 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=10 rc=crf mbtree=1 crf=21.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
Output #0, hls, to '/var/www/html/streams/tmp/aft.m3u8':
 Metadata:
 major_brand : isom
 minor_version : 512
 compatible_brands: isomiso2avc1mp41
 encoder : Lavf58.45.100
 Stream #0:0(und): Video: h264 (libx264), yuv420p, 720x384 [SAR 1:1 DAR 15:8], q=-1--1, 25 fps, 90k tbn, 25 tbc (default)
 Metadata:
 creation_time : 1970-01-01T00:00:00.000000Z
 handler_name : VideoHandler
 encoder : Lavc58.91.100 libx264
 Side data:
 cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: N/A
 Stream #0:1(und): Audio: aac (LC), 48000 Hz, stereo, fltp, 128 kb/s (default)
 Metadata:
 creation_time : 1970-01-01T00:00:00.000000Z
 handler_name : SoundHandler
 encoder : Lavc58.91.100 aac

pid: 4206
stderr: frame= 8 fps=0.0 q=0.0 size=N/A time=00:00:00.46 bitrate=N/A speed=0.931x 
pid: 4206
stderr: frame= 21 fps= 21 q=26.0 size=N/A time=00:00:00.96 bitrate=N/A speed=0.95x 
pid: 4206
stderr: frame= 33 fps= 22 q=26.0 size=N/A time=00:00:01.49 bitrate=N/A speed=0.982x 
pid: 4206
stderr: frame= 46 fps= 23 q=26.0 size=N/A time=00:00:02.00 bitrate=N/A speed=0.989x 
pid: 4206
stderr: frame= 58 fps= 23 q=26.0 size=N/A time=00:00:02.49 bitrate=N/A speed=0.986x 
pid: 4206



and so on...



Any help would be highly appreciated !

Many thanks in advance

-
VB.NET FFMPEG Stops
18 mars 2012, par Mcqueen_23Hi evryone i'm trying to convert files using ffmpeg
my codes only fetched
— -Skip--- Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'Enrique Iglesias - Tonight.mp4' : Metadata : major_brand : mp42 minor_version : 0 compatible_brands : isommp42 creation_time : 2011-03-20 19:07:02 Duration : 00:03:50.05, start : 0.000000, bitrate : 219 kb/s Stream #0.0(und) : Video : h264, yuv420p, 480x360 [PAR 1:1 DAR 4:3], 117 kb/s, 29.97 fps, 59.75 tbr, 1k tbn, 59.83 tbc Metadata : creation_time : 1970-01-01 00:00:00 Stream #0.1(und) : Audio : aac, 44100 Hz, stereo, s16, 95 kb/s Metadata : creation_time : 2011-03-20 19:07:03 Output #0, mp3, to 'Enrique Iglesias - Tonight.mp3' : Metadata : major_brand : mp42 minor_version : 0 compatible_brands : isommp42 TDEN : 2011-03-20 19:07:02 TSSE : Lavf52.94.0 Stream #0.0(und) : Audio : libmp3lame, 44100 Hz, stereo, s16, 64 kb/s Metadata : creation_time : 2011-03-20 19:07:03and cannot i cannot get the next lines ung
Proccess.ErrorDataReceived
eventHere are my Codes
Public Structure ItemStruct
Public ID, URL, FileName, FileExt, ConvertExt, ConvertQuery As String
Public FileSize As Int64
Public Method, status As Method
Public prog_bar As ProgressBar
Public DeleteOrigin, TrimStart, TrimEnd As Boolean
End Structure
Friend Class Converter
Public busy As Boolean = False
Private _Item As ItemStruct
Public Event ProgressChange(ByVal id As String, ByVal percent As Integer, ByVal etr As TimeSpan)
Public Event ConvertFinish(ByVal id As String)
Private m As Threading.Thread
Private WithEvents timer As New Timer With {.Interval = 100}
Public Sub New()
End Sub
Public Sub New(ByVal item As ItemStruct)
_Item = item
m = New Threading.Thread(AddressOf Convert)
End Sub
Public Sub Start()
m.Start()
timer.Start()
End Sub
Private duration As Decimal = 0.0F
Private current As Decimal = 0.0F
Private varIsSet As Boolean = False
Private Sub Convert()
Dim cmd As String = _Item.ConvertQuery
Dim inputName As String = _Item.URL
Dim fName As String = _Item.FileName & _Item.FileExt
Dim dir As String = _Item.URL.Replace(fName, "")
Dim ouputName As String = dir & _Item.FileName & "." & _Item.ConvertExt
cmd = Replace(cmd, "--i", inputName)
cmd = Replace(cmd, "--o", ouputName)
cmd = cmd.Remove(0, 6)
cmd = cmd.Trim
Dim proc As New Process
With proc.StartInfo
.FileName = Path.Combine(Application.StartupPath, "ffmpeg.exe")
.Arguments = cmd
proc.EnableRaisingEvents = False
.UseShellExecute = False
.CreateNoWindow = True
.RedirectStandardError = True
.RedirectStandardOutput = True
.RedirectStandardInput = True
AddHandler proc.ErrorDataReceived, AddressOf UpdateData
proc.Start()
proc.BeginErrorReadLine()
End With
End Sub
Public Sub Cancel()
m.Abort()
End Sub
Private Sub UpdateData(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
Dim s As String = e.Data
If s.Contains("Duration: ") Then
duration = GetDuration(s)
ElseIf s.Contains("frame=") Then
current = GetTime(s)
Else
Dim proc As Process = DirectCast(sender, Process)
Dim m As Match = Regex.Match(s, "^File\ '(.*?)'\ already\ exists", RegexOptions.IgnoreCase)
If m.Success Then
Dim w As StreamWriter = proc.StandardInput
If MessageBox.Show("File '" & m.Groups(1).ToString & "' already exists." & vbNewLine & "Do you want to Overwrite existing file?", "Overwrite", MessageBoxButtons.YesNo) = DialogResult.Yes Then
w.WriteLine("y")
Else
w.WriteLine("n")
End If
End If
'RaiseEvent ConvertFinish(_Item.ID)
'proc.WaitForExit()
'proc.Close()
End If
Debug.Print(s)
If Not duration And Not current Then varIsSet = False Else varIsSet = True
End Sub
Private Function GetDuration(ByVal s As String) As Double
Dim m As Match = Regex.Match(s, "Duration: ((.*?), (.*))")
If m.Success Then
Dim duration As String = m.Groups(2).ToString
Return TimeSpan.Parse(duration).TotalSeconds
End If
Return Nothing
End Function
Private Function GetTime(ByVal s As String) As Double
Dim m As Match = Regex.Match(s, "(.*) time=(.*) bitrate")
If m.Success Then
Dim currentTime As String = m.Groups(2).ToString
Return TimeSpan.Parse(currentTime).TotalSeconds
End If
Return Nothing
End Function
Private Sub timer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles timer.Tick
If varIsSet Then
Dim etr As TimeSpan = TimeSpan.FromSeconds(CInt(current / duration))
etr = New TimeSpan(etr.Hours, etr.Minutes, etr.Seconds)
RaiseEvent ProgressChange(_Item.ID, CInt((current / duration) * 100 * 100), etr)
End If
End Sub
End Class