• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ossia / score / 35742451820

22 Sep 2026 02:44PM UTC coverage: 40.337% (+0.3%) from 40.008%
35742451820

push

github

jcelerier
gfx: name DirectShow camera formats by codec, and widen the subtypes

A compressed pin carries no pixel format, so CameraSettings holds
AV_PIX_FMT_NONE for it and av_get_pix_fmt_name answers nullptr. The device
list's description built its name from that with only MJPEG special-cased,
which left every other compressed codec assigning a null pointer to a
std::string. Gfx::cameraFormatName names a format from its codec when it has
no layout and from its layout otherwise, and neither lookup is used
unchecked.

The compressed half of the subtype decision now goes to the fourcc -> codec
table FFmpeg maintains for RIFF/AVI, which is what ffmpeg's own dshow device
resolves a pin through, rather than the six-entry list it replaces. H.264's
eight spellings, DV's five, VP8/VP9/AV1, MPEG-4/DIVX/Xvid, H.263, JPEG 2000,
v210 and the lossless card codecs all arrive from there. The layout table is
consulted first, so a fourcc it names stays raw even where that table calls
it a codec -- Y41P, which we unpack.

What that table lacks and DirectShow emits is listed alongside it: H.265 in
both spellings in the wild (UVC cameras label the pin 'H265', OBS and the LAV
filters 'HEVC'), the CFCC/IJPG/TVMJ/WAKE/Plum Motion-JPEG fourccs from
dshow.h, and MDVF. A camera whose only pin is H.265 had no format to offer
at all.

The raw table gains the aliases FFmpeg's own raw table carries, so a pin
spelt any of them resolves to the layout we offer it under: HDYC (the UYVY
capture cards present), the other packed 4:2:2 spellings, the planar AVI
ones, YUV9, Y411 and 'Y16 '.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QnTb3Zd2ctRWoTs5wMtoiC

51 of 52 new or added lines in 3 files covered. (98.08%)

8408 existing lines in 137 files now uncovered.

119310 of 295781 relevant lines covered (40.34%)

112391.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.97
/src/plugins/score-lib-process/Process/MissingFiles.cpp
1
#include <Process/MissingFiles.hpp>
2
#include <Process/ProjectConsolidation.hpp>
3

4
#include <score/document/DocumentContext.hpp>
5
#include <score/tools/RecursiveWatch.hpp>
6
#include <score/tools/ThreadPool.hpp>
7

8
#include <QCoreApplication>
9
#include <QFileInfo>
10
#include <QMetaObject>
11
#include <QPointer>
12

13
#include <algorithm>
14
#include <string>
15

16
namespace Process
17
{
18

19
FileReport scanMissingFiles(const score::DocumentContext& ctx)
28✔
20
{
21
  const auto target = projectTarget(ctx);
28✔
22

23
  auto report = runFileOperation(
56✔
24
      ctx,
28✔
25
      [&](const ExternalFileRef& ref, FileEntry& e) -> QString {
42✔
26
    if(!ref.rewritable || ref.directory || ref.usage == FileUsage::Output
14✔
27
       || score::isRemoteUrl(ref.path))
14✔
28
    {
29
      // A remote stream has no file to find: locateFilePath would anchor it to
30
      // the document folder and every rtsp:// url would come back missing.
31
      e.action = FileAction::Unsupported;
1✔
32
      return {};
1✔
33
    }
34

35
    e.sourcePath = score::locateFilePath(ref.path, target.sourceRoots);
13✔
36
    if(e.sourcePath.isEmpty() || !QFileInfo::exists(e.sourcePath))
13✔
37
    {
38
      e.action = FileAction::Missing;
8✔
39
      return {};
8✔
40
    }
41

42
    e.action = FileAction::Unchanged;
5✔
43
    e.size = QFileInfo{e.sourcePath}.size();
5✔
44
    return {};
5✔
45
      },
14✔
46
      /*dryRun=*/true);
47

48
  report.projectFolder = target.folder;
28✔
49
  return report;
28✔
50
}
28✔
51

52
void FileIndex::scan(const QString& folder, FileScan* progress)
8✔
53
{
54
  m_root = folder;
8✔
55
  m_byName.clear();
8✔
56

57
  if(folder.isEmpty())
8✔
58
    return;
1✔
59

60
  // Kept alive for the whole walk: the traversal wants a zero-terminated root.
61
  const std::string root = folder.toStdString();
7✔
62

63
  QString lastFolder;
7✔
64
  score::for_all_files(root, [&](std::string_view path) {
233✔
65
    if(progress && progress->cancelled())
226✔
66
      return;
20✔
67
    if(path.empty())
206✔
UNCOV
68
      return;
×
69

70
    const QString p = QString::fromUtf8(path.data(), path.size());
206✔
71
    qsizetype sep = p.lastIndexOf('/');
206✔
72
#if defined(_WIN32)
73
    sep = std::max(sep, p.lastIndexOf('\\'));
74
#endif
75
    if(sep < 0 || sep == p.size() - 1)
206✔
UNCOV
76
      return;
×
77

78
    m_byName.insert(p.sliced(sep + 1).toLower(), p);
206✔
79

80
    if(progress)
206✔
81
    {
UNCOV
82
      progress->fileSeen();
×
83

84
      // Publish on folder change, not per file.
UNCOV
85
      if(QStringView{p}.first(sep) != lastFolder)
×
86
      {
UNCOV
87
        lastFolder = p.first(sep);
×
UNCOV
88
        progress->setCurrentFolder(lastFolder);
×
UNCOV
89
      }
×
UNCOV
90
    }
×
91
  });
226✔
92
}
8✔
93

94
FileScan::FileScan(QString folder)
2✔
95
    : m_root{std::move(folder)}
1✔
96
{
97
}
98

UNCOV
99
QString FileScan::currentFolder() const
×
100
{
UNCOV
101
  std::lock_guard l{m_mutex};
×
UNCOV
102
  return m_currentFolder;
×
UNCOV
103
}
×
104

UNCOV
105
void FileScan::setCurrentFolder(const QString& folder)
×
106
{
UNCOV
107
  std::lock_guard l{m_mutex};
×
108
  m_currentFolder = folder;
×
UNCOV
109
}
×
110

111
std::shared_ptr<FileScan>
UNCOV
112
FileScan::start(const QString& folder, QObject* context, OnFinished onFinished)
×
113
{
UNCOV
114
  auto self = std::make_shared<FileScan>(folder);
×
115

UNCOV
116
  score::TaskPool::instance().post(
×
UNCOV
117
      [self, ctx = QPointer<QObject>{context}, cb = std::move(onFinished)]() mutable {
×
UNCOV
118
    FileIndex index;
×
UNCOV
119
    index.scan(self->root(), self.get());
×
120

UNCOV
121
    QMetaObject::invokeMethod(
×
UNCOV
122
        QCoreApplication::instance(),
×
UNCOV
123
        [ctx, cb = std::move(cb), index = std::move(index),
×
UNCOV
124
         cancelled = self->cancelled()]() mutable {
×
UNCOV
125
      if(!ctx)
×
UNCOV
126
        return;
×
UNCOV
127
      cb(std::move(index), cancelled);
×
UNCOV
128
    },
×
129
        Qt::QueuedConnection);
UNCOV
130
      });
×
131

UNCOV
132
  return self;
×
UNCOV
133
}
×
134

135
std::vector<QString>
136
FileIndex::candidates(const QString& missingPath, qint64 size) const
9✔
137
{
138
  const QString name = QFileInfo{missingPath}.fileName().toLower();
9✔
139
  if(name.isEmpty())
9✔
UNCOV
140
    return {};
×
141

142
  std::vector<QString> found;
9✔
143
  for(auto it = m_byName.constFind(name); it != m_byName.constEnd() && it.key() == name;
17✔
144
      ++it)
8✔
145
    found.push_back(it.value());
8✔
146

147
  // Best first: a size match beats a name match, and among equals the file
148
  // closest to the folder the user pointed at is the likelier one.
149
  std::stable_sort(
9✔
150
      found.begin(), found.end(), [&](const QString& a, const QString& b) {
11✔
151
    if(size > 0)
2✔
152
    {
153
      const bool sa = QFileInfo{a}.size() == size;
1✔
154
      const bool sb = QFileInfo{b}.size() == size;
1✔
155
      if(sa != sb)
1✔
156
        return sa;
1✔
UNCOV
157
    }
×
158
    return a.count('/') < b.count('/');
1✔
159
      });
2✔
160

161
  return found;
9✔
162
}
18✔
163

164
FileReport
165
relinkFiles(const score::DocumentContext& ctx, const QHash<QString, QString>& chosen)
4✔
166
{
167
  const auto target = projectTarget(ctx);
4✔
168

169
  auto report = runFileOperation(
8✔
170
      ctx,
4✔
171
      [&](const ExternalFileRef& ref, FileEntry& e) -> QString {
9✔
172
    const auto it = chosen.constFind(ref.path);
5✔
173
    if(it == chosen.constEnd())
5✔
UNCOV
174
      return {};
×
175

176
    e.sourcePath = score::locateFilePath(ref.path, target.sourceRoots);
5✔
177
    e.destinationPath = *it;
5✔
178

179
    if(!QFileInfo::exists(e.destinationPath))
5✔
180
    {
181
      e.action = FileAction::Failed;
1✔
182
      e.note = QObject::tr("%1 does not exist").arg(e.destinationPath);
1✔
183
      return {};
1✔
184
    }
185

186
    // Store it relative when possible: a relink is a chance to make the
187
    // document more portable, not just to make it work once.
188
    e.newStoredPath
8✔
189
        = score::relativizeFilePath(e.destinationPath, target.destinationRoots);
8✔
190
    e.size = QFileInfo{e.destinationPath}.size();
4✔
191
    e.action = FileAction::Relinked;
4✔
192

193
    return e.newStoredPath;
4✔
194
      },
5✔
195
      /*dryRun=*/false, FileOperationKind::Relink);
196

197
  report.projectFolder = target.folder;
4✔
198
  return report;
4✔
199
}
4✔
200
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc