有损还是无损?


18

给定一个音频文件,请确定它是以有损格式还是无损格式编码的。出于此挑战的目的,仅需要对以下格式进行分类:

规则

  • 如果以文件名的形式输入,则不应对文件名做任何假设(例如,扩展名不能保证格式正确,甚至不存在)。
  • 输入文件中将不存在ID3或APEv2元数据。
  • 可以使用任何两个唯一且可区分的输出,例如01lossylosslessfoobar等等。

测试用例

测试用例迎接这一挑战包括位于一个zip文件这里包含两个目录:lossylossless。每个目录都包含多个音频文件,它们都是0.5秒的440 Hz正弦波,并以各种格式编码。所有音频文件都具有与上述格式匹配的扩展名,但A440.m4a(MPEG Layer 4容器中的AAC音频)除外。


MPEG Layer 4容器中的AAC音频 ”提出了一个问题:答案还需要处理其他哪些容器格式?
彼得·泰勒

@PeterTaylor仅特别提到了AAC,因为我无法找到一种方法,而不通过FFMPEG将AAC音频嵌入MPEG Layer 4容器中。Vorbis音频嵌入在Ogg容器中(这是Vorbis音频的规范)。所有其他格式均为独立格式。
Mego

您确定要使用TTA文件吗?根据规范,TTA文件应以魔术数字TTA1或TTA2开头。FFM2(文件的幻数)似乎与FFmpeg流相对应。Linux 文件可识别TTA1标头,但不能识别FFM2标头。
丹尼斯,

另外,我们是否可以假设AAC始终位于MPEG Layer 4标头中?如果没有,我们可以假设什么?
丹尼斯,

我们可以将文件的内容作为输入还是我们的代码必须检索它们?
毛茸茸的

Answers:


18

果冻7 5字节

ƈƈeØA

有损格式返回0,无损格式返回1

在线尝试!(Gist中的永久链接)

背景

我们必须支持的格式具有以下幻数,即,它们以这些字节开头。

Format    Header (text)       Header (hex)
-----------------------------------------------------------------------------------
AC3       .w                  0B 77
AMR       #!AMR               23 21 41 4D 52
AAC       ÿñP@..ü             FF F1 50 40 00 1F FC
  M4A     ... ftypM4A         00 00 00 20 66 74 79 70 4D 34 41 20
MP2       ÿû                  FF FB
MP3       ÿû                  FF FB
OGG       OggS                4F 67 67 53
WMA       0&²u.fÏ.¦Ù.ª.bÎl    30 26 B2 75 8E 66 CF 11 A6 D9 00 AA 00 62 CE 6C

AIFF      FORM????AIFF        46 4F 52 4D ?? ?? ?? ?? 41 49 46 46
FLAC      fLaC                66 4C 61 43
TTA       TTA1                54 54 41 31
  FFM2    FFM2                46 46 4D 32
WAV       RIFF????WAVE        52 49 46 46 ?? ?? ?? ?? 57 41 56 45

缩进条目是出现在测试用例中的先前格式的容器。?表示可变字节。.表示不可打印的字节。所有其他字节均以其ISO 8859-1字符显示。

通过仅查看第二个字节,我们可以轻松地确定格式:

无损格式的第二个字节为大写字母,而有损格式则没有。

怎么运行的

ƈƈeØA  Main link. No arguments.

ƈ      Read a char from STDIN and set the left argument to this character.
 ƈ     Read another char from STDIN and set the return value to this character.
   ØA  Yield the uppercase alphabet, i.e., "ABCDEFGHIJKLMNOPQRSTUVWXYZ".
  e    Exists; return 1 if the return value (second char on STDIN) belongs to the
       uppercase alphabet, 0 if not.

2
这是一个非常聪明的解决方案。
Mego

10

C,82 80 32字节

受到@Dennis的回答的启发,可以进一步减少:

main(){return getchar()&200^64;}

将文件数据通过管道传输到stdin。无损返回0,有损返回非零。

还是原来更长的支票:

char v[5];main(){scanf("%4c",v);return*v&&strstr("fLaC FORM RIFF TTA1 FFM2",v);}

将文件数据通过管道传输到stdin。返回非零(1)表示无损,或0表示有损。

据我所知,您列出的所有格式都有单独的幻数(AIFF / WAV除外,但无论如何都是无损的),因此这只是检查该幻数是否为已知的无损值。该*v&&只是为了防止它匹配以空字节(M4A)开头的文件。

我已经包含了我在规格表中找到的值(fLaC= FLAC,RIFF= WAV / AIFF,TTA1= TTA)以及FORM= AIFF和FFM2= TTA来自提供的示例文件(我只能猜测这些是包装器格式或更高版本)。


或更短的感觉似作弊的选择:

Bash +文件,61字节

N="$(file "$1")";[[ $N = *": d"* || $N = *IF* || $N = *FL* ]]

将文件名作为参数。无损返回0,有损返回非零。

完全符合您的期望;询问file文件类型是什么,然后检查已知模式。TTA比赛: d: data),AIFF / WAV比赛IF和FLAC比赛FL。无损结果均与这些结果都不匹配,并且我测试了删除文件名后它仍然可以正常工作。


测试:

for f in "$@"; do
    echo "Checking $f:";
    ./identify2 "$f" && echo "shorter C says LOSSLESS" || echo "shorter C says LOSSY";
    ./identify < "$f" && echo "longer C says LOSSY" || echo "longer C says LOSSLESS";
    ./identify.sh "$f" && echo "file says LOSSLESS" || echo "file says LOSSY";
done;

# This can be invoked to test all files at once with:
./identify_all.sh */*

如果文件扩展名不正确,您的Bash解决方案也可以使用吗?“不能保证扩展名不能正确使用格式”,因此您应该能够为文件提供错误的扩展名,并且仍然可以正常使用。
mbomb007'4

@ mbomb007我刚刚对扩展进行了测试,它仍然可以很好地识别它们。我认为file还是不信任扩展(将许多用户将png重命名为jpeg与将其转换一样!)
Dave

7

GS2,3个字节

◄5ì

有损格式返回0,无损格式返回1

在线尝试!(Gist中的永久链接)

背景

我们必须支持的格式具有以下幻数,即,它们以这些字节开头。

Format    Header (text)       Header (hex)
-----------------------------------------------------------------------------------
AC3       .w                  0B 77
AMR       #!AMR               23 21 41 4D 52
AAC       ÿñP@..ü             FF F1 50 40 00 1F FC
  M4A     ... ftypM4A         00 00 00 20 66 74 79 70 4D 34 41 20
MP2       ÿû                  FF FB
MP3       ÿû                  FF FB
OGG       OggS                4F 67 67 53
WMA       0&²u.fÏ.¦Ù.ª.bÎl    30 26 B2 75 8E 66 CF 11 A6 D9 00 AA 00 62 CE 6C

AIFF      FORM????AIFF        46 4F 52 4D ?? ?? ?? ?? 41 49 46 46
FLAC      fLaC                66 4C 61 43
TTA       TTA1                54 54 41 31
  FFM2    FFM2                46 46 4D 32
WAV       RIFF????WAVE        52 49 46 46 ?? ?? ?? ?? 57 41 56 45

缩进条目是出现在测试用例中的先前格式的容器。?表示可变字节。.表示不可打印的字节。所有其他字节均以其ISO 8859-1字符显示。

通过仅查看第二个字节,我们可以轻松地确定格式:

无损格式的第二个字节为大写字母,而有损格式则没有。

怎么运行的

     (implcit) Push the entire input from STDIN as a string on the stack.
◄    Push 1.
 5   Get the strings character at index 1, i.e., its second character.
  ì  Test if the character is an uppercase letter.

2

JavaScript(ES6),20个字节

c=>/^[fFRT]/.test(c)

说明

取文件的内容作为输入,并返回true,如果该文件是无损或false如果它是有损通过测试该输入的第一个字符,以确定它是一个fFRT


尝试一下

将文件内容粘贴到中textarea

f=
c=>/^[fFRT]/.test(c)
i.addEventListener("input",_=>console.log(f(i.value)))
<textarea id=i></textarea>


第二次尝试,81 63字节

从提供的URL提取文件的内容,事实证明这是过分的。

u=>fetch(u).then(r=>r.text()).then(t=>alert(/^[fFRT]/.test(t)))

首先努力,146个 116 89字节

由于mime类型与扩展名相关联,因此无效,并且显然,响应标头可以作为附加输入。

u=>fetch(u).then(r=>alert(/aiff|flac|tta|wave|wav$/.test(r.headers.get("Content-Type"))))

Web服务器通常基于文件扩展名生成MIME,这违反了此处的规则。您是否检查了它是否适用于不带扩展名的文件?(如果确实如此,那么您可能应该将所使用的服务器名称作为“语言”的一部分)
Dave

1
@戴夫很确定他们没有。MIME和扩展名完全不依赖。如果更改文件的扩展名并上载,则MIME类型是文件实际内容的MIME,而不是扩展名。但是,实际上不允许将输入作为URL。我不确定。
mbomb007 '04

@ mbomb007我不确定你为什么这么说;mime类型是Internet事物,而不是文件系统/文件事物,据我所知,服务器将使用配置的查找基于扩展名来确定它(出于提供报头的速度;它们不想在提供服务之前检查每个文件它)。以Apache AddType <mime> <extension>或IIS 为例<MimeMap>。当然,特定的设置或文件托管工具可以进行适当的检查,这应该使服务器选择成为答案的一部分(因为是服务器确定文件类型!)
Dave

1
我已经使用.NET完成了文件验证,即使扩展名在上传前已更改,MIME类型也与内容匹配。
mbomb007 '17

@ mbomb007,那么无论您使用的哪个.NET组件都必须在上载过程中或在提供文件时执行了文件检查(我想在上载过程中会提高性能,但您永远不会知道)。因此,回到我的原始评论,这将使此答案类似“ JavaScript + .NET SeverLibraryXYZ”。至于从URL获取输入,我可以看到您为什么犹豫,但就提及服务器选择而言,我个人认为它是有效的。也许上面有一个现成的元,但是最终当然要取决于Mego。
戴夫

1

芯片,11字节

~Z~S
t'G~aF

在Chip中无耻地复制了Dennis的果冻答案。

无损收益0x0,有损收益0x1

在线试用,链接为要点(感谢丹尼斯的TIO策略)

说明!

~Z~S
t'

这部分是内部管理:它S擦除第一个字节,并t在第二个字节之后终止。

G~aF

这是决定的实质。每个输入字节都由这些位访问HGFEDCBA。如果G是一套,而F不是,这意味着该字节在此范围内0x40,以0x5f(这大致相当于“大写”,并不够好手头的任务)。

但是,为了节省字节,我将此决定从转换G and (not F)(not G) or F,因为or可以隐含在Chip中。

然后,将得到的真/假值放入a,这是输出的最低位。(所有其他位将为零)。在TIO中,我通过hexdump运行输出,以便显示值。

等效地,在C-ish中,人们会说:

out_byte = !(in_byte & 0x40) && (in_byte & 0x20)

1

Cubix,16个字节

$-!u'HIa'@/1@O<

净形式:

    $ -
    ! u
' H I a ' @ / 1
@ O < . . . . .
    . .
    . .

自己尝试

您应该在单独的列表中输入文件的十进制字节值。分隔符无关紧要,只要不是数字或减号就可以。该代码实际上只关心第一个字节,因此,您可以根据需要省略文件的其余部分。程序输出0无损和1有损。在这里尝试!默认输入使用FLAC标头。

说明

关于文件的好处是(几乎)所有文件都有所谓的魔术。这些是文件的前几个字节。好的软件不会检查文件扩展名,而是检查文件魔术,以查看它是否可以处理某个文件。

丹尼斯(Dennis)找到了一种使用这种魔术来找到压缩类型的方法,但是他丢弃了第一个字节的事实使我想尝试提出一种使用第一个字节而不是第二个字节的方法。毕竟,这个社区只是关于保存字节。

这是不同文件类型的前几个字节的列表。我将它们分为两组:有损和无损。这是十进制,十六进制和二进制的第一个字节的值。您可能已经看到一种模式...

Lossy:                  Lossless:
255:0xFF:0b11111111     102:0x66:0b01100110
 79:0x4F:0b01001111      84:0x54:0b01010100
 35:0x23:0b00100011      82:0x52:0b01010010
 11:0x0B:0b00001011      70:0x46:0b01000110
  0:0x00:0b00000000

我看到的模式是,第二位(从左到右计数)始终在“无损”字节上,而第五位始终在关闭。此组合不会以任何有损格式出现。要“提取”此代码,我们只需做一个二进制AND(由0b01001000 (=72)),然后与进行比较0b01000000 (=64)。如果两者相等,则输入格式为无损,否则为有损。

可悲的是,Cubix没有这样的比较运算符,所以我使用了减法(如果结果为64,则得出0,否则结果为8,-56或-64。稍后我将再讲到。

首先,让我们从程序的开头开始。二进制AND使用以下a命令完成:

'HIa
'H   # Push 0b01001000 (72)
  I  # Push input
   a # Push input&72

然后,我们使用减法与64进行比较(请注意,我们撞到了一个镜子,该镜子将IP反射到该部分中间的顶面(第一行,第二个字符,指向南)。

'@-
'@  # Push 0b01000000 (64)
  - # Subtract from (input&72)
    # Yields 0 for lossy, non-zero otherwise

在IP被翻转之后,如果(且仅当)栈顶为非零时u,我们使用一些控制流将a推1入栈:

!$1
!   # if top = 0:
 $1 #   do nothing
    # else:
  1 #   push 1

在包装完立方体之后,我们点击了<指令,该指令将IP指向第四行。剩下要做的就是输出并终止。

O@
O  # Output top of the stack as number
 @ # End program

因此,程序输出0无损和1有损。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.